16

I'm trying to do something which I think should be simple: do a blocking read from standard input, but timing out after a specified interval if no data is available.

In the Unix world this would be simple with select() but that doesn't work in Windows because stdin isn't a socket. What's the next simplest option without creating extra threads etc?

I'm using visual C++ targeting a Win32 environment.

so far I have tried:

  1. using select (doesn't work if the input is not a socket)

  2. using WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE)). - Remy's first suggestion. This always seems to return immediately when you call it if the standard input is a console (others have reported the same problem)

  3. using overlapped IO and doing a WaitForSingleObject (Remy's third suggestion). In this case the read always seems to block when the input is coming from a console - it seems that stdin does not support asynchronous I/O.

At the moment I'm thinking my only remaining option is to create a thread which will do a blocking read and then signal an event, and then have another thread which waits for the event with a timeout.

StaceyGirl
  • 7,242
  • 12
  • 35
  • 64
Andy
  • 9,149
  • 11
  • 61
  • 85
  • 1
    You explained your problem well, but what have you tried for that? – masoud Nov 13 '13 at 13:59
  • 1
    Your #2 attempt with WaitForSingleObject() works if you properly remove the initial events from STD_INPUT_HANDLE. In my case I have a "focus" event in the console queue from the start. Check out @Clay 's answer, it's correct. – Vladimir Sinenko Oct 26 '15 at 05:46

6 Answers6

6

I had to solve a similar problem. On Windows it is not as easy or obvious as Linux. It is, however, possible. The trick is that Windows places console events in the console input event queue. You've got to filter out the events you don't care about and only process those events you do care about (like key presses).

For further reading: see the Win32 console documentation

Here is some mostly-debugged sample code based on a socket and stdin multiplexer I was working on:

void ProcessStdin(void)
{
    INPUT_RECORD record;
    DWORD numRead;
    if(!ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &record, 1, &numRead)) {
        // hmm handle this error somehow...
        return;
    }

    if(record.EventType != KEY_EVENT) {
        // don't care about other console events
        return;
    }

    if(!record.Event.KeyEvent.bKeyDown) {
        // really only care about keydown
        return;
    }

    // if you're setup for ASCII, process this:
    //record.Event.KeyEvent.uChar.AsciiChar

} // end ProcessStdin

int main(char argc, char* argv[])
{
    HANDLE eventHandles[] = {
        GetStdHandle(STD_INPUT_HANDLE)
        // ... add more handles and/or sockets here
        };

    DWORD result = WSAWaitForMultipleEvents(sizeof(eventHandles)/sizeof(eventHandle[0]), 
        &eventHandles[0], 
        FALSE, 
        1000, 
        TRUE
        );

    switch(result) {
        case WSA_WAIT_TIMEOUT: // no I/O going on right now
            break;

        case WSA_WAIT_EVENT_0 + 0: // stdin at array index 0
            ProcessStdin();
            break;

        case WSA_WAIT_EVENT_0 + 1: // handle/socket at array index 1
            break;

        case WSA_WAIT_EVENT_0 + 2: // ... and so on
            break;

        default: // handle the other possible conditions
            break;
    } // end switch result
}
Clay
  • 1,149
  • 1
  • 9
  • 19
4

Using GetStdHandle + WaitForSingleObject works fine. But be sure to set the approriate flags and flush the console buffer as well before entering the loop.

In short (without error checks)

std::string inStr;
DWORD fdwMode, fdwOldMode;
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
GetConsoleMode(hStdIn, &fdwOldMode);
// disable mouse and window input
fdwMode = fdwOldMode ^ ENABLE_MOUSE_INPUT ^ ENABLE_WINDOW_INPUT;
SetConsoleMode(hStdIn, fdwMode);
// flush to remove existing events
FlushConsoleInputBuffer(hStdIn);
while (!abort)
{
    if (WaitForSingleObject(hStdIn, 100) == WAIT_OBJECT_0)
    {
         std::getline(std::cin, inStr);
    }
}
// restore console mode when exit
SetConsoleMode(hStdIn, fdwOldMode);
StaceyGirl
  • 7,242
  • 12
  • 35
  • 64
Jörgen
  • 306
  • 3
  • 10
  • Use `ReadConsoleInput` instead of `getline` and avoid disabling other events. – dashesy Oct 20 '16 at 01:38
  • 3
    This toggles `ENABLE_MOUSE_INPUT` and `ENABLE_WINDOW_INPUT` instead of disabling them, which is a bug. Also, that's not enough. `WaitForSingleObject` will still detect focus-changing and menu events, which will cause `ReadConsole` or `getline` to block if there's nothing in the buffer. – alexchandel Dec 06 '16 at 21:59
3

This should do it:

int main()
{
    static HANDLE stdinHandle;
    // Get the IO handles
    // getc(stdin);
    stdinHandle = GetStdHandle(STD_INPUT_HANDLE);

    while( 1 )
    {
        switch( WaitForSingleObject( stdinHandle, 1000 ) )
        {
        case( WAIT_TIMEOUT ):
            cerr << "timeout" << endl;
            break; // return from this function to allow thread to terminate
        case( WAIT_OBJECT_0 ):
            if( _kbhit() ) // _kbhit() always returns immediately
            {
                int i = _getch();
                cerr << "key: " << i << endl;
            }
            else // some sort of other events , we need to clear it from the queue
            {
                // clear events
                INPUT_RECORD r[512];
                DWORD read;
                ReadConsoleInput( stdinHandle, r, 512, &read );
                cerr << "mouse event" << endl;
            }
            break;
        case( WAIT_FAILED ):
            cerr << "WAIT_FAILED" << endl;
            break;
        case( WAIT_ABANDONED ): 
            cerr << "WAIT_ABANDONED" << endl;
            break;
        default:
            cerr << "Someting's unexpected was returned.";
        }
    }

    return 0;
}
flux
  • 190
  • 3
  • 11
  • There is a problem with this. If events, such as mouse move, continue to arrive then WaitForSingleObject() never gets to timeout so you never get the case( WAIT_TIMEOUT ). You need another mechanism to check the time elapsed on ever loop. – flux Feb 24 '14 at 05:13
  • 1
    The `stdinHandle` can be redirected. You must check the type of the handle before call an appropriate API function: `GetFileType`. `FILE_TYPE_DISK` -> `ReadFile`; `FILE_TYPE_CHAR` -> `ReadConsoleInput` or `PeekConsoleInput`; `FILE_TYPE_PIPE` -> `ReadFile` or `PeekNamedPipe`. Otherwise `ReadConsoleInput` will fail. – Andry May 27 '21 at 18:33
2

In case anyone is writing chrome native messaging host and is looking for solution to check if there is any input on stdin without blocking then this works perfect:

HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
int timer = GetTickCount();
while(timer+10000 > GetTickCount())
{
    unsigned int length = 0;
    DWORD bytesAvailable = 0; 
    PeekNamedPipe(hStdin,NULL,0,NULL,&bytesAvailable,NULL);
    if(bytesAvailable > 0)
    {
        for (int i = 0; i < 4; i++)
        {
            unsigned int read_char = getchar();
            length = length | (read_char << i*8);
        }


        for (int i = 0; i < length; i++)
        {
            msg += getchar();
        }
        timer = GetTickCount();
    }
    else
    {
        // nothing to read, stdin empty
        Sleep(10);
    }
}
dejv25
  • 21
  • 1
  • Fantastic - after trying many different ways to check for available data before reading, this was the only way that worked. – user3896248 Feb 25 '19 at 15:51
1

Use GetStdHandle() to get the stdin handle. You can then either:

  1. use WaitForSingleObject() i the stdin handle itself to detect when there is console input available for reading, then read from it as needed.

  2. use GetNumberOfConsoleInputEvents() or PeekConsoleInput() on the stdin handle in a loop to determine when there is data available for reading, then read from it as needed.

  3. use ReadFile() with an OVERLAPPED structure containing an event handle, then use the event handle with WaitForSingleObject() to detect if the read times out.

In any case, be careful if stdin has been redirected. If it is redirected to something than is not console I/O, you can't use a GetStdHandle() handle with console functions. If it is redirected to a file, you have to use ReadFile().

Remy Lebeau
  • 505,946
  • 29
  • 409
  • 696
  • Thanks Remy - This is getting more complex all the time :( Actually I'm writing a google chrome native messaging host. in the real world it will be invoked by the chrome process with standard input and output connected to that, but I was assuming it would be simple to just use generic input/output functions so that I could test it from the command line. It seems that in the world of windows console applications, these things are not so straightforward as *nix! It looks like option 3 is probably the most likely to work – Andy Nov 14 '13 at 08:53
0

You'll need the GetStdHandle function to obtain a handle to the console, then you can use WaitForSingleObject to wait for an event to happen on that handle, with a timeout.

StaceyGirl
  • 7,242
  • 12
  • 35
  • 64
Pat
  • 1,696
  • 10
  • 18
  • D'oh! thanks. I was faffing about with Asynchronous I/O and SleepEx. this looks like what I was looking for all along :-) – Andy Nov 13 '13 at 14:18
  • hmm after some more playing around this doesn't seem work. WaitForSingleObject on stdin always seems to return WAIT_OBJECT_0 when you initially call it, even though a read from stdin would block. – Andy Nov 13 '13 at 16:06
  • According to the documentation: "A process can specify a console input buffer handle in one of the wait functions to determine when there is unread console input. When the input buffer is not empty, the state of a console input buffer handle is signaled." Maybe there is something on the console input but you are trying to read more than there is available? – Remy Lebeau Nov 13 '13 at 20:48
  • Other people seem to have had this same problem - I think it may be that there are other events (e.g. mouse events) which aren't standard input but still cause the object to appear signalled. As you'll see below, my app won't be invoked from the console in the real world (that wwas just for testing) so I'm trying to avoid this and use some more generic mechanism instead. – Andy Nov 14 '13 at 08:56