2011-10-21 8 views
8

Encontré this question que pregunta cómo leer la entrada de forma asíncrona, pero solo funcionará con los descriptores de flujo POSIX, que no funcionarán en Windows. Entonces, encontré this tutorial que muestra que en lugar de usar un descriptor de flujo POSIX puedo usar un boost::asio::windows::stream_handle.¿Cómo leer de forma asíncrona la entrada desde la línea de comando usando boost asio en Windows?

Siguiendo los dos ejemplos, obtuve el siguiente código. Cuando lo ejecuto, no puedo escribir nada en el símbolo del sistema, ya que el programa termina inmediatamente. Me gustaría capturar cualquier entrada del usuario, posiblemente en un std::string, mientras que permite que se ejecute otra lógica dentro de mi programa (es decir, realizar E/S asincrónicas desde una consola de Windows).

Esencialmente, estoy tratando de evitar el bloqueo de mi programa cuando intenta leer desde stdin. No sé si esto es posible en Windows, ya que también encontré this post que detalla los problemas que otro usuario encontró al intentar hacer lo mismo.

#define _WIN32_WINNT 0x0501 
#define INPUT_BUFFER_LENGTH 512 

#include <cstdio> 
#include <iostream> 

#define BOOST_THREAD_USE_LIB // For MinGW 4.5 - (https://svn.boost.org/trac/boost/ticket/4878) 
#include <boost/bind.hpp> 
#include <boost/asio.hpp> 

class Example { 
    public: 
     Example(boost::asio::io_service& io_service) 
      : input_buffer(INPUT_BUFFER_LENGTH), input_handle(io_service) 
     { 
      // Read a line of input. 
      boost::asio::async_read_until(input_handle, input_buffer, "\r\n", 
       boost::bind(&Example::handle_read, this, 
        boost::asio::placeholders::error, 
        boost::asio::placeholders::bytes_transferred)); 
     } 
     void handle_read(const boost::system::error_code& error, std::size_t length); 
     void handle_write(const boost::system::error_code& error); 
    private: 
     boost::asio::streambuf input_buffer; 
     boost::asio::windows::stream_handle input_handle; 
}; 

void Example::handle_read(const boost::system::error_code& error, std::size_t length) 
{ 
    if (!error) 
    { 
     // Remove newline from input. 
     input_buffer.consume(1); 
     input_buffer.commit(length - 1); 

     std::istream is(&input_buffer); 
     std::string s; 
     is >> s; 

     std::cout << s << std::endl; 

     boost::asio::async_read_until(input_handle, input_buffer, "\r\n", 
      boost::bind(&Example::handle_read, this, 
       boost::asio::placeholders::error, 
       boost::asio::placeholders::bytes_transferred)); 
    } 
    else if(error == boost::asio::error::not_found) 
    { 
     std::cout << "Did not receive ending character!" << std::endl; 
    } 
} 

void Example::handle_write(const boost::system::error_code& error) 
{ 
    if (!error) 
    { 
     // Read a line of input. 
     boost::asio::async_read_until(input_handle, input_buffer, "\r\n", 
      boost::bind(&Example::handle_read, this, 
       boost::asio::placeholders::error, 
       boost::asio::placeholders::bytes_transferred)); 
    } 
} 

int main(int argc, char ** argv) 
{ 
    try { 
     boost::asio::io_service io_service; 
     Example obj(io_service); 
     io_service.run(); 
    } catch(std::exception & e) 
    { 
     std::cout << e.what() << std::endl; 
    } 
    std::cout << "Program has ended" << std::endl; 
    getchar(); 
    return 0; 
} 
+0

No soy usuario de Windows, pero ¿no usa \ r \ n como un nuevo indicador de línea? –

+0

Sí, pero ¿eso evitará que funcione, ya que \ n todavía está en la secuencia de nueva línea? Cambié la cadena delímetro a "\ r \ n", el mismo resultado. – nickb

+0

donde invocar io_service :: run()? –

Respuesta

3

Acabo de pasar una hora o dos investigando este tema, así que decidí publicar para evitar que otros pierdan el tiempo.

Windows no es compatible con IOCP para identificadores de entrada/salida estándar. Cuando toma el identificador por GetStdHandle(STD_INPUT_HANDLE), el identificador no tiene configurado FILE_FLAG_OVERLAPPED, por lo que no admite IO superpuesto (asincrónico). Pero incluso si

CreateFile(L"CONIN$", 
    GENERIC_READ, 
    FILE_SHARE_READ, 
    NULL, 
    OPEN_EXISTING, 
    FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 
    NULL); 

WinAPI simplemente ignorar dwFlagsAndAttributes y de nuevo vuelve el mango que no admite solapada IO. La única forma de obtener asincrónico IO de la entrada/salida de la consola es usar el asa con WaitForSingleObject con 0 tiempo de espera para que pueda verificar si hay algo que leer sin bloqueo. No es exactamente asíncrono IO, pero puede evitar el subprocesamiento múltiple si se trata de un objetivo.

Más detalles acerca de la API de consola: https://msdn.microsoft.com/en-us/library/ms686971(v=VS.85).aspx

Cuál es la diferencia entre las manijas devueltas por GetStdHandle y CreateFile se describe aquí: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075(v=vs.85).aspx. En resumen, la diferencia es solo para procesos secundarios cuando CreateFile puede dar acceso a su búfer de entrada de consola, incluso si se redirigió en el proceso principal.

3

Debe inicializar su stream_handle en el identificador de entrada de la consola. No puede usar el mismo stream_handle para la entrada y la salida porque son dos controles diferentes.

Para la entrada:

Example() 
     : /* ... */ input_handle(io_service, GetStdHandle(STD_INPUT_HANDLE)) 

Para la salida se usaría CONSOLE_OUTPUT_HANDLE. Pero es probable que sea demasiado, es poco probable que introduzca tantos datos en la ventana estándar que necesite usar una escritura asíncrona.

+2

Esto tiene sentido, pero provoca una excepción: "assign: The handle is invalid". Además, necesitaba cambiar 'CONSOLE_INPUT_HANDLE' a' STD_INPUT_HANDLE', por MSDN, para poder compilarlo. – nickb

+2

Quizás esta es la razón: http://comments.gmane.org/gmane.comp.lib.boost.asio.user/2394 – nickb

5

Debe invocar io_service::run() a start el bucle de procesamiento de eventos para operaciones asincrónicas.

class Example { 
    public: 
     Example(boost::asio::io_service& io_service) 
      : io_service(io_service), input_buffer(INPUT_BUFFER_LENGTH), input_handle(io_service) 
     { 
     } 
     void start_reading(); 
     void handle_read(const boost::system::error_code& error, std::size_t length); 
     void handle_write(const boost::system::error_code& error); 
    private: 
     boost::asio::io_service& io_service; 
     boost::asio::streambuf input_buffer; 
     boost::asio::windows::stream_handle input_handle; 
}; 

int main(int argc, char * argv) 
{ 
    boost::asio::io_service io_service; 
    Example obj(io_service); 
    obj.start_reading(); 

    io_service.run(); 

    return 0; 
} 
+0

Gracias por su ayuda Sam. Modifiqué mi pregunta anterior para incluir el código completo que tengo ahora. Incorporé su respuesta, pero ahora cuando invoco el programa, instantáneamente regresa y sale exitosamente (sin estrellarse). ¿Por qué volvería cuando io_service.run() debería bloquear? – nickb

+2

@nickb porque nunca asignó el controlador a nada, por lo que no puede leer de él. – SoapBox

Cuestiones relacionadas