Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to asynchronously read input from command line using boost asio in Windows?

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;
}
like image 895
nickb Avatar asked Oct 21 '11 20:10

nickb


1 Answers

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.

like image 108
Andriy Tylychko Avatar answered Sep 29 '22 10:09

Andriy Tylychko