Boost C++ Libraries Home Libraries People FAQ More

PrevUpHomeNext

Handbook

Header files
Classes
this_process
Properties
Launch Modes
Coroutines

Boost.Process is a header-only library. It comes with a convenience header file which is the only one you need to include to make use of all library features:

#include <boost/process.hpp>
[Note] Note

The header files boost/process/posix.hpp and boost/process/windows.hpp are not included by boost/process.hpp. They provide platform-specific extensions.

The child class holds a handle to the child process. It can be direclty used to launch a process via the constructor. It is not copyable, but movable.

This class does provides three functions to wait for the process to exit.

wait()
wait_for(duration)
wait_until(time_point);

If the process exits, the exit_code will be stored inside the class.

Another way to stop the process is to terminate it. This will also produce an exit code which is platform-specific.

It is implemented via TerminateProcess and kill -9 on posix.

After a process has exited the exit code is stored either inside the class or the operating system holds it. The return value of the exit code is undefined if the process did not exit. The latter can be checked by calling is_running.

The child will automatically terminate the child process on destruction, if it wasn't waited for or detached.

The synchronous pipe is implemented as a the template basic_pipe, which can be used with any char type. The typedefs pipe and wpipe provide typedefs for use with char and wchar_t.

Pipes can be copied, since handles can be duplicated.

Based on the basic_pipe template, a stream implementation, similar to the streams in the standard C++ library.

basic_pstreambuf
basic_pistream
basic_postream
basic_pstream

The asynchronous pipe class is implemented similar to the I/O objects provided by boost.asio.

A async_pipe can be converted into and constructed from a basic_pipe.

[Caution] Caution

Async pipes use named pipes on windows. Therefore an exception is thrown if a pipe was not constructed as a named pipe and is converted to an async_pipe

The group object is implemented similar to child. It does however not provide an exit code.

[Caution] Caution

A groups behaviour is undefined when it was not used during the launch of a process.

The environment can be stored in two different classes.

- [classref boost::process::native_environment native_environment]
- [classref boost::process::environment environment]

This behave simliar to an std::map, in that you can access the variables by the operator[] with a string key. The returned value is acutally a proxy, which can be interpreted as a single string or a list of ';' seperated strings. The latter is used by variables like "PATH".

The native_environment represents the current environment. It essentially works like a shared_ptr, though several copies may not represent the changes done by one.

native_entironment n1;
native_entironment n2;

n1["MY_VAR"] = "Thingy";
n2["MY_VAR"]; //will be empty.

The plain environment class is meant to be used when starting a new process. It can be constructed from a native_environment.

Similar to std::this_thread, the boost::process library provides a boost::this_process namespace which allows to obtain and modify the property of the current process.

It provides the following functions.

Get the process id of the current process.

int get_id();

Get the native handle of the current process.

native_handle_t native_handle();

Get the enviroment of the current process.

native_environment environment();

Get the path environment variable of the current process runs.

std::vector< std::string > path();

Get the current working directory of the current process. std::string cwd();

Cmd

#include <boost/process/cmd.hpp>

The cmd property allows to explicitly set commands for the execution.

system(cmd="gcc --version");

For any range or single value the following expressions are valid:

//assign
cmd="value";
cmd(value);

The overload form applies when only one string is passed to a launching function. The string will be internally parsed and split at spaces.

#include <boost/process/args.hpp>

The args property allows to explicitly set arguments for the execution.

system("gcc", args={"--version"});

For any range or single value (including std::intializer_list) the following expressions are valid:

//assign
args="value";
args(value);
args={"val1", "val2"};
args({"val1", "val2"});

//append
args="value";
args={"val1", "val2"};

The overload form is used when more than one string is passed, from the second one forward. I.e. the following expressions are equivalent:

spawn("gcc", "--version");
spawn("gcc", args="--version");

[Note] Note

A string will be parsed and set in quotes if it has none and contains spaces.

Exe

#include <boost/process/exe.hpp>

The exe initializer allows to explicitly set arguments for the execution.

system(exe="gcc");

For any range or single value the following expressions are valid:

//assign
exe="value";
exe(value);

The overload form applies for the first string argument when several string are passed. It will be used from any position if a boost::filesystem::path is passed.

#include <boost/process/shell.hpp>

The shell property enables to launch a program through the shell of the system.

system("gcc", shell);

The shell argument goes without any expression. The operator() is overloaded, to obtain the path of the system shell.

auto shell_cmd = shell();
[Caution] Caution

Launching through the shell will NOT provide proper error handling, i.e. you will get an error via the return code

#include <boost/process/error.hpp>

The error property will set the executor to handle any errors by setting an error_code.

std::error_code ec;
system("gcc", error(ec));

The overload is achieved by just passing an std::error_code to the function.

The property has two aliases:

  • error_ref
  • error_code
[Note] Note

This will disable the exception.

#include <boost/process/error.hpp>

The ignore_error property will disable any error handling. This can be useful on linux, where error handling will require a pipe.

#include <boost/process/error.hpp>

The throw_on_error property will enable the exception when launching a process. It is unnecessary by default, but may be used, when an additional error_code is provided.

#include <boost/process/async.hpp>

An io_service must be passed to enable the asynchronous functionality. It only exists in the explicit form.

#include <boost/process/async.hpp>

When an io_service is passed, the on_exit property can be used, to be notified when the child process exits.

io_service ios;
spawn("ls", on_exit=[](int exit, const std::error_code& ec_in){});

The following syntax is valid:

on_exit=function;
on_exit(function);

I/O

#include <boost/process/io.hpp>

The most complex part in this library is the I/O with the child process. There are five modes that can be used.

  • File I/O
  • Synchronouse Pipe
  • Asynchronous Pipe
  • Closing
  • Redirecting to null

The properties described here are

  • std_in
  • std_out
  • std_err

which should not bee confused with stdin etc.

Additionally, the output streams can be combined via &, i.e. the following code is valid:

system("program", (std_out & std_err ) > "log.txt");

This property has no overload variant.

The file I/O simple redirects the stream to a file, where the valid types are

  • boost::filesystem::path
  • std::string
  • const char*
  • FILE*

FILE* is explicitly added, so the process can easily redirect the output stream of the child to another output stream of the process. That is:

system("ls", std_out > stderr);

A syntax like system("ls", std_out > std::cerr) is not possible, due to the C++ implementation not providing access to the handle.

The valid expressions for this property are

std_in < file;
std_in = file;
std_out > file;
std_out = file;
std_err > file;
std_err = file;
(std_out & std_err) > file;
(std_out & std_err) = file;

Here's an example:

boost::filesystem::path log    = "my_log_file.txt";
boost::filesystem::path input  = "input.txt";
boost::filesystem::path output = "output.txt";
system("my_prog", std_out>output, std_in<input, std_err>log);

As explained in the corresponding section, the boost.process library provides a pipe class which can be used to communicate with child processes.

[Note] Note

Technically the async_pipe works synchronous here, since no asio implementation is used by the library here. The async-operation will then however not end if the process is finished, since the pipe remains open. You can use the async_close function with on_exit to fix that.

Valid types for pipe I/O are the following:

  • basic_pipe
  • async_pipe
  • basic_ipstream
  • basic_opstream
  • basic_pstream

Valid expressions with pipes are these:

std_in < pipe;
std_in = pipe;
std_out > pipe;
std_out = pipe;
std_err > pipe;
std_err = pipe;
(std_out & std_err) > pipe;
(std_out & std_err) = pipe;

Note that the pipe may also be used between several processes, like this:

pipe p;
child c1("nm", "a.out", std_out>p);
chlid c2("c++filt", std_in<p);

Asynchronous Pipe I/O classifies communication which has automatically handling of the async operations by the process library. This means, that a pipe will be constructed, the async_read/-write will be automatically started, and that the end of the child process will also close the pipe.

Valid types for pipe I/O are the following:

  • boost::asio::const_buffer [1]
  • boost::asio::mutable_buffer [2]
  • boost::asio::streambuf
  • std::future<std::string> [3]
  • `std::future<std::vector<char>> [4]

Valid expressions with pipes are these:

std_in < buffer;
std_in = buffer;
std_out > buffer;
std_out = buffer;
std_err > buffer;
std_err = buffer;
(std_out & std_err) > buffer;
(std_out & std_err) = buffer;

[Note] Note

It is also possible to get a future for std_in, by chaining another std::future<void> onto it, i.e.

std::future<void> fut; std::string data; spawn("prog", std_in < buffer(data) > fut); fut.get();

[Note] Note

boost::asio::buffer is also available in the boost::process namespace.

Closing a stream means, that no data can be written or read from it.

This can be achieved by the following syntax.

std_in < close;
std_in = close;
std_in.close();
std_out > close;
std_out = close;
std_out.close();
std_err > close;
std_err = close;
std_err.close();
(std_out & std_err) > close;
(std_out & std_err) = close;
(std_out & std_err).close();

Unlike closing a pipe, redirecting to null will allow to read and write data. Written data will disregarded, while read data will only consist of EOF. This can be achieved by the following syntax.

std_in < close;
std_in = close;
std_in.close();
std_out > close;
std_out = close;
std_out.close();
std_err > close;
std_err = close;
std_err.close();
(std_out & std_err) > buffer;
(std_out & std_err) = buffer;
(std_out & std_err).close();

#include <boost/process/start_dir.hpp>

To set the start dir, the start_dir property is provided. There is no overload format for it.

It can be used with std::string and boost::filesystem::path.

system("ls", start_dir="./bin");

#include <boost/process/group.hpp>

Groups can be used to group processes. There is no guarantee that the launched process is not also in the same group as the father process.

[Note] Note

On Posix this is usally the case, while it depends on the group the fathers process is in on windows.

As an example:

group g;
child c1("prog1", g);
child c2("prog2", g);

g.terminate(); //terminate c1 and c2. 

There is no explicit property for this feature.

env

#include <boost/process/env.hpp>

The env property provides a functional way to modify the environment used by the child process. If none is passed the environment is inherited from the father process. Appending means that the environment will be interpreted as a ';' seperated list as used in PATH.

The following expressions are valid.

//assign or create variable
env[NAME] = VALUE;
env[NAME] = {VAL1, VAL2};
env(NAME, VALUE);
env(NAME, {VAL1, VAL2});

//append to variable
env[NAME] += VALUE;
env[NAME] += {VAL1, VAL2};

//reset the variable
env[NAME]=boost::none;
env(NAME, boost::none);

//set the whole environment
env(environment());
env=environment();

As an example:

spawn("b2", env["PATH"]+="F:/boost", env["SOME_VAR"]=boost::none, env["NEW_VAR"]="VALUE");

The overload style is done by passing an instance of environment. The above example would look like this.

environment e = this_process::environment();
e["PATH"]   += "F:/boost";
e.erase("SOME_VAR");
e["NEW_VAR"] = "VALUE";
spawn("b2", e);

[Caution] Caution

Passing an empty environment will cause and error.

The standard version is to create a child, which will spawn the process.

child c("ls");

This will create an attached child process.

As a convenience, the boost::process::system function is provided. It works as std::system, though it allows all the properties boost.process provides. It will execute the process and wait for it's exit; then return the exit_code.

int ret = system("ls");
[Caution] Caution

When used with Pipes it will almost always result in a dead-lock.

When using this function with an asynchronous properties and NOT passing an io_service object, the system function will create one and run it. When the io_service is passed to the function, the system function will check if it is active, and call the io_service::run function if not.

[Tip] Tip

This function also allows to get a boost::asio::yield_context passed to use coroutines, which will cause the stackful coroutine to yield and return when the process is finished. See this section for an example.

Another function which is more than convenience is the boost::process::spawn function. This function starts a process and immediately detaches it. It thereby prevents the system from creating a zombie process, but will also cause the system to be unable to wait for the child to exit.

spawn("ls");
[Caution] Caution

There is no guarantee that the child process will survive the father process terminating. This is dependant on the Operating System.

[Note] Note

This function does not allow the usage of

Stackless coroutines can be implemented rather easily, so there is no need to implement extra functionality concerning boost.process.

struct stackless_t : boost::asio::coroutine
{
    child c;

    boost::asio::io_service & ios;

    stackless_t(boost::asio::io_service & ios) : ios(ios) {}

    void operator()(
            boost::system::error_code ec = boost::system::error_code(),
            std::size_t n = 0)
    {
        if (!ec) reenter (this)
        {
             c = child("my_program", ios,
                    bp::on_exit=
                    [this](int, const std::error_code&)
                    {
                        (*this)(); //this is the reentry for the coroutine
                    });
            yield; //yield the thing.
        }
    }
};
///post the coroutine to a io-service and run it
int main()
{
    boost::asio::io_service ios;
    ios.post(stackless_t(ios));
    ios.run();
    return 0;
}

For stackful coroutines this is not as simple, because the members of boost::asio::yield_context are not documented. Using a stackful coroutine could be implemented like this:

void cr(boost::asio::yield_context yield_) //my coroutine
{
    auto coro = yield_.coro.lock();
    child c("my-program",
        on_exit=
            [coro](int, const std::error_code & ec)
            {
                auto &c = *coro; //renter the coroutine.
                if (c)
                    c();
            });

    yield_.ca(); //yield and return when the process is finished.
}

This example still has a problem: when using async-io the pipe buffer might not be complete yet, which is why we should posted to the io-service. In order to simplify this problem, boost.process provides a simple way to use stackful coroutines, which looks as follows:

void cr(boost::asio::yield_context yield_)
{
    bp::system("my-program", yield_);
}

This will automatically suspend the coroutine until the program is finished.



[1] Constructed with boost::asio::buffer, only for std_in

[2] Constructed with boost::asio::buffer

[3] std_out & std_err only

[4] std_out & std_err only


PrevUpHomeNext