The existing answers don't address the case where the standard input is an anonymous pipe rather than a console. In this case functions like GetNumberOfConsoleInputEvents()
will return 0x6
(bad handle) and the methods described above will not work.
In my case I am trying to use stdin and stdout to facilitate asynchronous interprocess communication, so the parent process (nodejs) opens stdin and stdout as anonymous pipes with a child process (c++). For this case, the type of stdin can be detected as follows:
HANDLE stdinput = GetStdHandle(STD_INPUT_HANDLE);
if (stdinput == INVALID_HANDLE_VALUE) {
DWORD problem1 = GetLastError();
cout << "Failed to get input handle. " << (void*) problem1 << endl;
return(1);
}
DWORD fileType = GetFileType(stdinput);
if (fileType != FILE_TYPE_PIPE) {
cout << "Input type is not pipe. Instead: " << (void*) fileType << endl;
return(2);
}
Then, as to enable asynchronous reads from this input pipe, I came up with two ideas:
Method 1: Continuously poll in a loop for available input
do {
DWORD bytesAvailable = 0;
BOOL success = PeekNamedPipe(stdinput, NULL, NULL, NULL, &bytesAvailable, NULL );
if (!success) {
cout << "Couldn't run PeekNamedPipe." << endl;
DWORD problem = GetLastError();
cout << "Error code: " << (void*)problem << endl;
}
char buf[bytesAvailable+1]; //mingw allows dynamic stack allocation. In Visual studio might need to allocate on heap.
if (bytesAvailable > 0) {
ReadFile(stdinput, buf, additionalBytesAvailable, NULL, NULL);
cout << "Received: " << buf << endl;
}
Sleep(10); //Small delay between checking for new input
} while(1);
Method 1 suffers from the issue that input can't be processed any faster than the small delay. Of course the delay could be shortened, but then the thread will consume more CPU resources. For this reason I came up with an alternate method.
Method 2: Block using ReadFile and send to different thread for processing. In the input processing thread, which will block when waiting for input:
do {
char firstChar[2]; //we will read the first byte not sure if it is null terminated...
//block until at least one byte is available
ReadFile(stdinput, firstChar, 1, NULL, NULL);
DWORD additionalBytesAvailable = 0;
BOOL success = PeekNamedPipe(stdinput, NULL, NULL, NULL, &additionalBytesAvailable, NULL );
if (!success) {
cout << "Couldn't run PeekNamedPipe." << endl;
DWORD problem = GetLastError();
cout << "Error code: " << (void*)problem << endl;
}
char buf[additionalBytesAvailable+2]; //mingw allows stack allocation.
buf[0] = firstChar[0];
buf[1] = '\0';
if (additionalBytesAvailable > 0) {
ReadFile(stdinput, buf+1, additionalBytesAvailable, NULL, NULL);
}
std::cout << count << " Read: " << buf << endl;
//write some data to a different thread that is still responsive
pthread_mutex_lock(&responsiveThreadLock);
mutexProtectedString = std::string(buf);
pthread_mutex_unlock(&responsiveThreadLock);
PostThreadMessage(handleOfResponsiveThread, WM_NEWINPUT, NULL, NULL);
} while(1);
And in the thread that stays responsive:
MSG msg;
do {
GetMessageWithTimeout(&msg, 1000);
if (msg.message == WM_NEWINPUT) {
std::string receivedStringCopy = "";
pthread_mutex_lock(&responsiveThreadLock);
receivedStringCopy = mutexProtectedString;
pthread_mutex_unlock(&responsiveThreadLock);
std::cout << "Received: " << receivedStringCopy << endl;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
std::cout << "Still responsive. " << endl;
} while(1);
GetMessageWithTimeout is a function designed to stay responsive (after a timeout) while waiting for a message:
//Wait upto timeoutMs milliseconds for a message.
//Return TRUE if a message is received or FALSE if the timeout occurs or there is an error.
BOOL GetMessageWithTimeout(MSG *msg, UINT timeoutMs)
{
//Check the message queue and return immediately if there is a message available
BOOL hasMessage = PeekMessage(msg, NULL, 0, 0, PM_REMOVE);
if (hasMessage) {
return(TRUE);
}
else {
//Any new messages that have arrived since we last checked the message queue will
//cause MsgWaitForMultipleObjects to return immediately.
//otherwise this will block the thread until a message arrives or timeout occurs
DWORD res1 = MsgWaitForMultipleObjects(0, NULL, FALSE, timeoutMs, QS_ALLINPUT);
if (res1 == WAIT_TIMEOUT) {
printf("!");
return(FALSE);
}
if (res1 == WAIT_OBJECT_0) {
//If we are here, there *should* be a message available. We can just get it with PeekMessage
hasMessage = PeekMessage(msg, NULL, 0, 0, PM_REMOVE);
return(hasMessage);
}
//If we get here, its because we have a WAIT_FAILED. Don't know why this would occur, but if it
//does, lets pause for a bit, so we don't end up in a tight loop
Sleep(100);
return(FALSE);
}
}
This second method will respond immediately to new inputs. For additional robustness, it may be necessary to check and make sure that the contents of the pipe ends with a \n
(so incomplete messages aren't sent) and to push messages to a vector so that multiple messages don't override one another if the receiving thread can't process them fast enough.