1

I have some simple application:

//first.exe

int main()
{
  std::cout << "1" << std::endl;
  std::cout << "2" << std::endl;
  std::cout << "3" << std::endl;

  std::cin.get();

  return 0;
}

And I want to invoke a callback in separate application when "2" occurs in stdout of application run as a child process:

//second.exe

int main()
{
  boost::asio::io_context context;
  boost::process::async_pipe out(context);
  boost::asio::streambuf buffer;

  boost::process::child("first.exe", boost::process::std_out > out);

  boost::asio::async_read_until(out, buffer, "2", [](boost::system::error_code code, std::size_t size)
  {
    //do something
  });

  context.run();

  return 0;
}

The problem is that I have to press any button in parent process due to std::cin.get() in child process to make io_context execute callback function. Do you have any suggestions to make it work as intended?

Kasata Ata
  • 365
  • 3
  • 11

1 Answers1

1

In this case I don't think you need to do a thing (the std::cin read might just fail):

Live On Coliru

#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <boost/process/async.hpp>
#include <iostream>
#include <iomanip>

int main()
{
    namespace bp = boost::process;
    boost::asio::io_context context;
    bp::async_pipe out(context);

    bp::child c("./first.exe", bp::std_out > out);

    boost::asio::streambuf buffer;
    boost::asio::async_read_until(out, buffer, "2", [&](boost::system::error_code code, std::size_t size) {
        if (code) {
            std::cerr << "Oops: " << code.message() << std::endl;
        } else {
            std::cerr << "received: " << size << " bytes: ";

            auto b = buffers_begin(buffer.data()), m = b+size, e = buffers_end(buffer.data());

            std::clog << std::quoted(std::string(b, m)) << std::endl;
            std::clog << "Note that we read more bytes: " << std::quoted(std::string(m, e)) << std::endl;

            buffer.consume(size);
        }
    });

    context.run();
    return c.exit_code();
}

Prints

received: 3 bytes: "1
2"
Note that we read more bytes: "
3
"

Cleaner

For cleanness, you could simply close std_in:

bp::child c("./first.exe", bp::std_out > out, bp::std_in.close());

And to be proper, also add the context for async io:

bp::child c("./first.exe", bp::std_out > out, bp::std_in.close(), context);

Those all work (see live).

More complicated

What if you actually need to supply input to get output? Or you need to supply input based on the output? Then the commenter is right: attach a pipe to std_in (or have a buffer be written asynchronously).

Sending a Fixed Buffer

Live On Coliru

  • first.cpp

    #include <iostream>
    int main() {
        using namespace std;
        string s;
        while (cin >> s)
            cout << "reversed: " << string(s.rbegin(), s.rend()) << endl;
    }
    
  • main.cpp

    #include <boost/process.hpp>
    #include <boost/asio.hpp>
    #include <boost/process/async.hpp>
    #include <iostream>
    #include <iomanip>
    
    int main() {
        namespace bp = boost::process;
        boost::asio::io_context context;
        bp::async_pipe out(context);
    
        std::string i = "hello\nwo2rld\n";
        bp::child c("./first.exe", bp::std_out > out, bp::std_in < boost::asio::buffer(i), context);
    
        boost::asio::streambuf buffer;
        boost::asio::async_read_until(out, buffer, "2", [&](boost::system::error_code code, std::size_t size) {
            if (code) {
                std::cerr << "Oops: " << code.message() << std::endl;
            } else {
                std::cerr << "received: " << size << " bytes: ";
    
                auto b = buffers_begin(buffer.data()), m = b+size, e = buffers_end(buffer.data());
    
                std::clog << std::quoted(std::string(b, m)) << std::endl;
                std::clog << "Note that we read more bytes: " << std::quoted(std::string(m, e)) << std::endl;
    
                buffer.consume(size);
            }
        });
    
        context.run();
        return c.exit_code();
    }
    

Prints

received: 30 bytes: "reversed: olleh
reversed: dlr2"
Note that we read more bytes: "ow
"

Sending Dynamic Data

Both synchronously and asynchronously:

#include <boost/process.hpp>
#include <boost/asio.hpp>
#include <boost/process/async.hpp>
#include <iostream>
#include <iomanip>

int main() {
    namespace bp = boost::process;
    boost::asio::io_context context;
    bp::async_pipe out(context), in(context);

    bp::child c("./first.exe", bp::std_out > out, bp::std_in < in, context);

    boost::asio::write(in, boost::asio::buffer("hello ", 6));

    boost::asio::streambuf buffer;
    boost::asio::async_read_until(out, buffer, "2", [&](boost::system::error_code code, std::size_t size) {
        if (code) {
            std::cerr << "Oops: " << code.message() << std::endl;
        } else {
            std::cerr << "received: " << size << " bytes: ";

            auto b = buffers_begin(buffer.data()), m = b+size, e = buffers_end(buffer.data());

            std::clog << std::quoted(std::string(b, m)) << std::endl;
            std::clog << "Note that we read more bytes: " << std::quoted(std::string(m, e)) << std::endl;

            buffer.consume(size);
        }
    });

    boost::asio::async_write(in, boost::asio::buffer("wo2rld\n", 7), [&](boost::system::error_code code, std::size_t size) {
        if (code) {
            std::cerr << "Oops: " << code.message() << std::endl;
        } else {
            std::cerr << "sent: " << size << " bytes: ";
        }
    });

    context.run();
    return c.exit_code();
}

Printing again:

sent: 7 bytes: received: 30 bytes: "reversed: olleh
reversed: dlr2"
Note that we read more bytes: "ow
"
sehe
  • 374,641
  • 47
  • 450
  • 633
  • It helped. Thanks. But now I am wondering what is the difference between cin.get() and getchar() because I tried first solution on both and it works only for cin. – Kasata Ata Sep 17 '19 at 08:02