Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Win32 - read from stdin with timeout

Tags:

c++

c

stdio

winapi

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.

like image 717
Andy Avatar asked Nov 13 '13 13:11

Andy


2 Answers

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
}
like image 165
Clay Avatar answered Oct 24 '22 04:10

Clay


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);
like image 4
Jörgen Avatar answered Oct 24 '22 06:10

Jörgen