Win32: lee de stdin con tiempo de espera

Intento hacer algo que creo que debería ser simple: hacer una lectura de locking desde la entrada estándar, pero agotar el tiempo después de un intervalo especificado si no hay datos disponibles.

En el mundo de Unix esto sería simple con select() pero eso no funciona en Windows porque stdin no es un socket. ¿Cuál es la siguiente opción más simple sin crear hilos adicionales, etc.?

Estoy usando Visual C ++ apuntando a un entorno Win32.

hasta ahora lo he intentado:

  1. using select (no funciona si la entrada no es un socket)

  2. utilizando WaitForSingleObject(GetStdHandle(STD_INPUT_HANDLE)) . – La primera sugerencia de Remy. Esto siempre parece regresar inmediatamente cuando lo llamas si la entrada estándar es una consola (otros han informado del mismo problema)

  3. usando IO superpuesto y haciendo un WaitForSingleObject (la tercera sugerencia de Remy). En este caso, la lectura siempre parece bloquearse cuando la entrada proviene de una consola: parece que stdin no admite E / S asíncronas.

Por el momento estoy pensando que mi única opción restante es crear un hilo que haga una lectura de locking y luego señalar un evento, y luego tener otro hilo que espera el evento con un tiempo de espera.

Tuve que resolver un problema similar. En Windows no es tan fácil u obvio como Linux. Sin embargo, es posible. El truco es que Windows coloca eventos de consola en la cola de eventos de entrada de la consola. Tienes que filtrar los eventos que no te interesan y solo procesar aquellos eventos que realmente te interesan (como pulsaciones de teclas).

Para obtener más información: consulte la documentación de la consola Win32

Aquí hay un código de muestra depurado en su mayoría basado en un socket y multiplexor stdin en el que estaba trabajando:

 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 } 

Esto debería hacerlo:

 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; } 

El uso de GetStdHandle + WaitForSingleObject funciona bien. Pero asegúrese de configurar los indicadores correspondientes y eliminar el búfer de la consola también antes de ingresar al bucle.

En resumen (sin verificaciones de errores)

 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); } } // restre console mode when exit SetConsoleMode(hStdIn, fdwOldMode); 

Use GetStdHandle() para obtener el controlador stdin. Entonces puede:

  1. use WaitForSingleObject() el controlador stdin mismo para detectar cuándo hay una entrada de consola disponible para leer, luego lea de ella según sea necesario.

  2. utilice GetNumberOfConsoleInputEvents() o PeekConsoleInput() en el controlador stdin en un bucle para determinar cuándo hay datos disponibles para leer, luego lea de ellos según sea necesario.

  3. utilice ReadFile() con una estructura OVERLAPPED que contenga un identificador de evento, luego use el identificador de evento con WaitForSingleObject() para detectar si la lectura agota el tiempo de espera.

En cualquier caso, tenga cuidado si stdin ha sido redirigido. Si se redirige a algo que no es E / S de consola, no puede usar un GetStdHandle() con funciones de consola. Si se redirige a un archivo, debe usar ReadFile() .

En caso de que alguien esté escribiendo un host de mensajería nativo de cromo y esté buscando una solución para verificar si hay alguna entrada en stdin sin locking, entonces esto funciona perfecto:

 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); } } 

Necesitará la función GetStdHandle para obtener un control para la consola, luego puede usar WaitForSingleObject para esperar que ocurra un evento en ese identificador, con un tiempo de espera excedido.