Pipe reading bug

Discussion of chess software programming and technical issues.

Moderator: Ras

Cardoso
Posts: 365
Joined: Thu Mar 16, 2006 7:39 pm
Location: Portugal
Full name: Alvaro Cardoso

Pipe reading bug

Post by Cardoso »

Hi, I made a GUI in VB6 to be used with my checkers engine.
My engine is in c++. The protocol is 'half' winboard, the GUI sends commands via a pipe.
Each command is a character above 100, followed by some text and ended with "END" plus the linefeed character.
For each command I created I also created a 'received signal'.
For example:
const int vb2pb_DoMove = 110;
const int pb2vb_DoMove_Received = 111;
const int vb2pb_Search = 114;
const int pb2vb_Search_Received = 115;
const int vb2pb_DoMoveAndSearch = 116;
const int pb2vb_DoMoveAndSearch_Received = 117;
const int vb2pb_SetupPosition = 118;
const int pb2vb_SetupPosition_Received = 119;

So for each command my GUI sends it waits in a loop by the received answer from the engine.
These received responses from the engine to the GUI are sent to a hidden textbox in the GUI.
That's why I call it half winboard. This scheme was made in 2001.
So it worked all these years. But lately I decided to go without the received signal from the engine, because I want to speed up some operations like fast take backs and fast move forwards to browse complete games using the arrow keys.
But a problem arises. Usually there are several messages in the pipe (separated by the LF char).
I use the following function to read a line from the pipe:

Code: Select all

string ReadPipe(void) {
   string text = "";	
   getline(cin, text);
   return text;
}
This function does work in the sense it reads a single line until the LF code.
The problem is that it deletes the remaining messages waiting in the pipe.
The messages are indeed separated by the LF code, no doubts on that, since I checked each char ascii code.
So I changed the ReadPipe function to show the pipe before and after the 'getline(cin, text);' instruction.

Code: Select all

string ReadPipe(void) {
   string text = "";
   PrintGeneralText("ReadPipe: immediately befor 'getline(cin, text);' PipeContents=*" + PipeContents(hStdin) + "*", 1);
   getline(cin, text);
   PrintGeneralText("ReadPipe: message red: " + Msg2Descriptor((unsigned char)text[0]) + "   *" + text + "*", 1);
   PrintGeneralText("ReadPipe: immediately after 'getline(cin, text);' PipeContents=*" + PipeContents(hStdin) + "*", 1);
   return text;
}
Here's a tipical output (sorry for the long text, also I surround the pipe contents with asteriscs just to be sure there are no other characters leading/trailing the messages).

Code: Select all

ReadPipe: immediately befor 'getline(cin, text);' PipeContents=*Ç-1,24-31,00000000000000000000000B0B00000WbEND
€END
n-1,24-31,00000000000000000000000B0B00000WbEND
§ 1,0000000000000000000000000B0000BWw,InfiniteAnalysisEND
*
ReadPipe: message red: vb2pb_SetOpponentMoveInfo   *Ç-1,24-31,00000000000000000000000B0B00000WbEND*
ReadPipe: immediately after 'getline(cin, text);' PipeContents=**
Here's the description of the output:
there are 3 messages waiting in the pipe, the funny characters Ç € § are the codes that identify the messages, each message ends with "END" plus the LF char. After posting the output here in talkchess there is a line feed after each "END" string as you can see, wich doesn't happen in notepad.
The getline(cin, text); line does read properly the first message, but removes everything from the pipe as shown by the 2 asteriscs **
So the other 2 messages are simply gone.
To read the pipe I use the following function:

Code: Select all

string PipeContents(HANDLE hReadPipe) {
	DWORD nBufferSize, lpBytesRead, lpTotalBytesAvail, lpBytesLeftThisMessage;
	size_t find;
	string msg;

	if (PeekNamedPipe(hReadPipe, NULL, 0, NULL, &lpTotalBytesAvail, NULL) && lpTotalBytesAvail>0) { //'first det. if there are bytes waiting to be red
		nBufferSize = lpTotalBytesAvail;
		string buff(nBufferSize + 2, ' ');

		if (PeekNamedPipe(hReadPipe, (LPVOID)buff.c_str(), nBufferSize, &lpBytesRead, &lpTotalBytesAvail, &lpBytesLeftThisMessage)) {
			find = buff.find(Lf);
			if (find == string::npos) {
				PrintGeneralText("Error in PipeContents: Lf not found in buff!");
				Print_to_logfile("Error in PipeContents: Lf not found in buff!");
			}
			else {
				msg = my_Left(buff, lpTotalBytesAvail);
				if (msg.length()>0) {
					return msg;
				}
			}
		}
	}
	return "";
}
So What am I doing wrong? Why getline() clears the pipe completely if the are 2 more LF chars in it?
thanks in advance,
Alvaro[
bob
Posts: 20943
Joined: Mon Feb 27, 2006 7:30 pm
Location: Birmingham, AL

Re: Pipe reading bug

Post by bob »

Cardoso wrote:Hi, I made a GUI in VB6 to be used with my checkers engine.
My engine is in c++. The protocol is 'half' winboard, the GUI sends commands via a pipe.
Each command is a character above 100, followed by some text and ended with "END" plus the linefeed character.
For each command I created I also created a 'received signal'.
For example:
const int vb2pb_DoMove = 110;
const int pb2vb_DoMove_Received = 111;
const int vb2pb_Search = 114;
const int pb2vb_Search_Received = 115;
const int vb2pb_DoMoveAndSearch = 116;
const int pb2vb_DoMoveAndSearch_Received = 117;
const int vb2pb_SetupPosition = 118;
const int pb2vb_SetupPosition_Received = 119;

So for each command my GUI sends it waits in a loop by the received answer from the engine.
These received responses from the engine to the GUI are sent to a hidden textbox in the GUI.
That's why I call it half winboard. This scheme was made in 2001.
So it worked all these years. But lately I decided to go without the received signal from the engine, because I want to speed up some operations like fast take backs and fast move forwards to browse complete games using the arrow keys.
But a problem arises. Usually there are several messages in the pipe (separated by the LF char).
I use the following function to read a line from the pipe:

Code: Select all

string ReadPipe(void) {
   string text = "";	
   getline(cin, text);
   return text;
}
This function does work in the sense it reads a single line until the LF code.
The problem is that it deletes the remaining messages waiting in the pipe.
The messages are indeed separated by the LF code, no doubts on that, since I checked each char ascii code.
So I changed the ReadPipe function to show the pipe before and after the 'getline(cin, text);' instruction.

Code: Select all

string ReadPipe(void) {
   string text = "";
   PrintGeneralText("ReadPipe: immediately befor 'getline(cin, text);' PipeContents=*" + PipeContents(hStdin) + "*", 1);
   getline(cin, text);
   PrintGeneralText("ReadPipe: message red: " + Msg2Descriptor((unsigned char)text[0]) + "   *" + text + "*", 1);
   PrintGeneralText("ReadPipe: immediately after 'getline(cin, text);' PipeContents=*" + PipeContents(hStdin) + "*", 1);
   return text;
}
Here's a tipical output (sorry for the long text, also I surround the pipe contents with asteriscs just to be sure there are no other characters leading/trailing the messages).

Code: Select all

ReadPipe: immediately befor 'getline(cin, text);' PipeContents=*Ç-1,24-31,00000000000000000000000B0B00000WbEND
€END
n-1,24-31,00000000000000000000000B0B00000WbEND
§ 1,0000000000000000000000000B0000BWw,InfiniteAnalysisEND
*
ReadPipe: message red: vb2pb_SetOpponentMoveInfo   *Ç-1,24-31,00000000000000000000000B0B00000WbEND*
ReadPipe: immediately after 'getline(cin, text);' PipeContents=**
Here's the description of the output:
there are 3 messages waiting in the pipe, the funny characters Ç € § are the codes that identify the messages, each message ends with "END" plus the LF char. After posting the output here in talkchess there is a line feed after each "END" string as you can see, wich doesn't happen in notepad.
The getline(cin, text); line does read properly the first message, but removes everything from the pipe as shown by the 2 asteriscs **
So the other 2 messages are simply gone.
To read the pipe I use the following function:

Code: Select all

string PipeContents(HANDLE hReadPipe) {
	DWORD nBufferSize, lpBytesRead, lpTotalBytesAvail, lpBytesLeftThisMessage;
	size_t find;
	string msg;

	if (PeekNamedPipe(hReadPipe, NULL, 0, NULL, &lpTotalBytesAvail, NULL) && lpTotalBytesAvail>0) { //'first det. if there are bytes waiting to be red
		nBufferSize = lpTotalBytesAvail;
		string buff(nBufferSize + 2, ' ');

		if (PeekNamedPipe(hReadPipe, (LPVOID)buff.c_str(), nBufferSize, &lpBytesRead, &lpTotalBytesAvail, &lpBytesLeftThisMessage)) {
			find = buff.find(Lf);
			if (find == string::npos) {
				PrintGeneralText("Error in PipeContents: Lf not found in buff!");
				Print_to_logfile("Error in PipeContents: Lf not found in buff!");
			}
			else {
				msg = my_Left(buff, lpTotalBytesAvail);
				if (msg.length()>0) {
					return msg;
				}
			}
		}
	}
	return "";
}
So What am I doing wrong? Why getline() clears the pipe completely if the are 2 more LF chars in it?
thanks in advance,
Alvaro[
You have to watch out for buffering. The best way to read from a pipe (in C) is to use the read() system call, it reads what you ask, and stops, leaving other data alone. In C, if you use some of the buffered I/O stuff like gets and such, you get into trouble. The library will gulp in ALL the data in the pipe at once, and pass you the first line. If you look at the pipe to see if there is more data, none shows up. But if you call gets() again, you get the next item.

read() will give you everything in the pipe, up to the byte count you specify. It would be up to you to find the first line feed, and use the data up to that point, then find the next linefeed and use that data, etc.

If you look at the engine interface description that comes with xboard/winboard, you will find a lot of information that Tim Mann and I wrote up on this topic 20 years ago or so.