10

I found this question which asks how to read input asynchronously, but will only work with POSIX stream descriptors, which won't work on Windows. So, I found this tutorial which shows that instead of using a POSIX stream descriptor I can use a boost::asio::windows::stream_handle.

Following both examples I came up with the code below. When I run it, I cannot type anything into the command prompt, as the program immediately terminates. I'd like it to capture any input from the user, possibly into a std::string, while allowing other logic within my program to execute (i.e. perform asynchronous I/O from a Windows console).

Essentially, I'm trying to avoid blocking my program when it attempts to read from stdin. I do not know if this is possible in Windows, as I also found this post which details problems another user encountered when trying to do the same thing.

#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;
}
Community
  • 1
  • 1
nickb
  • 59,313
  • 13
  • 108
  • 143
  • I am not a Windows user, but doesn't it use \r\n as a new line indicator? – Sam Miller Oct 21 '11 at 21:04
  • Yes, but will that prevent it from working, since \n is still in the newline sequence? I changed the delimeter string to "\r\n", same result. – nickb Oct 21 '11 at 21:06
  • where do invoke io_service::run()? – Sam Miller Oct 21 '11 at 21:08
  • 1
    `io_service.run()` isn't invoked anywhere, should it be? Where would be the correct place? – nickb Oct 21 '11 at 21:20
  • you need to invoke `io_service::run()` somewhere, I've added an [answer](http://stackoverflow.com/questions/7855222/how-to-asynchronously-read-input-from-command-line-using-boost-asio-in-windows/7855564#7855564). – Sam Miller Oct 21 '11 at 21:38
  • 2
    Gotta love those anonymous downvoters. – nickb Nov 16 '12 at 15:09

3 Answers3

6

I just spent an hour or two investigating this topic so decided to post to prevent others to waste their time.

Windows doesn't support IOCP for standard input/output handles. When you take the handle by GetStdHandle(STD_INPUT_HANDLE), the handle doesn't have FILE_FLAG_OVERLAPPED set so it doesn't support overlapped (async) IO. But even if you

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

WinAPI just ignore dwFlagsAndAttributes and again returns the handle that doesn't support overlapped IO. The only way to get async IO of console input/output is to use the handle with WaitForSingleObject with 0 timeout so you can check if there's anything to read non-blocking. Not exactly async IO but can avoid multithreading if it's a goal.

More details about console API: https://msdn.microsoft.com/en-us/library/ms686971(v=VS.85).aspx

What's the difference between handles returned by GetStdHandle and CreateFile is described here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075(v=vs.85).aspx. In short the difference is only for a child processes when CreateFile can give access to its console input buffer even if it was redirected in the parent process.

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
5

You need to invoke io_service::run() to start the event processing loop for asynchronous operations.

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;
}
Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • Thanks for your help Sam. I modified my question above to include the complete code that I have now. I incorporated your answer, but now when I invoke the program, it instantly returns and exits successfully (not crashing). Why would it return when io_service.run() should block? – nickb Oct 21 '11 at 22:18
  • 2
    @nickb because you never assigned the handle to anything, so you can't read from it. – SoapBox Oct 21 '11 at 23:27
3

You need to initialize your stream_handle to the console input handle. You can't use the same stream_handle for input and for output because those are two different handles.

For input:

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

For output you would use CONSOLE_OUTPUT_HANDLE. But that is probably overkill, you're unlikely to be pushing that much data into stdout on windows that you'd need to use an async write.

nickb
  • 59,313
  • 13
  • 108
  • 143
SoapBox
  • 20,457
  • 3
  • 51
  • 87
  • 2
    This makes sense, but it causes an exception to be thrown - "assign: The handle is invalid". Also, I needed to change `CONSOLE_INPUT_HANDLE` to `STD_INPUT_HANDLE`, per MSDN, in order to get it to compile. – nickb Oct 21 '11 at 21:43