PrevUpHomeNext

Tutorial

Header files
Namespaces
Starting a program
Cleaning up resources
Handling errors
Setting command line arguments
Starting in a specific work directory
Inheriting environment variables
Setting up standard streams
Synchronous I/O
Asynchronous I/O
Waiting for a program to exit
Terminating a program

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>

All examples in this tutorial assume this header file is included.

[Note] Note

The header file boost/process/mitigate.hpp is not included by boost/process.hpp. It contains helpers to mitigate platform differences. As these helpers can have subtle side effects, boost/process/mitigate.hpp must be included explicitly.

Most definitions can be found in one namespace:

boost::process

For example, the most important function execute to start a program is defined in boost::process.

There is a second namespace used by Boost.Process:

boost::process::initializers

This namespace is used for initializers. Initializers are the parameters passed to execute. As you commonly use initializers provided by Boost.Process, you need to use this namespace, too.

The function Boost.Process provides to start a program is execute. You need to pass at least one initializer as a parameter. Think of initializers as named parameters. With the initializer run_exe you refer to the program you want to start:

execute(run_exe("test.exe"));

run_exe expects an exact filename. For instance, you can't leave out the file extension. run_exe does not automatically search for a program either. In the example above Boost.Process expects to find test.exe in the current work directory.

You can refer to programs in other directories with an absolute or relative path. It is also possible to use boost::filesystem::path:

boost::filesystem::path exe = "../test.exe";
execute(run_exe(exe));

Boost.Process provides two utility functions to lookup executables: Call search_path if you want to find an executable in the directories of the environment variable PATH. Or call shell_path for the system's shell.

If you have a long-running program starting other programs with execute, you want to be careful cleaning up resources. Resources are for example entries in the system's process table on POSIX and handlers to child processes on Windows.

The only reliable cross-platform solution to clean up resources is to call wait_for_exit:

child c = execute(run_exe("test.exe"));
wait_for_exit(c);

wait_for_exit is a blocking function. The function returns when the child process exits. Calling wait_for_exit guarantees that all resources are cleaned up.

There are other platform-specific solutions to clean up resources. Boost.Process provides the macros BOOST_POSIX_API and BOOST_WINDOWS_API if you need to distinguish between platforms. On POSIX you can call signal for example to ignore SIGCHLD:

#if defined(BOOST_POSIX_API)
    signal(SIGCHLD, SIG_IGN);
#endif
    execute(run_exe("test.exe"));

If SIGCHLD is ignored, a child process isn't added to the system's process table. There is no need then to call wait_for_exit. It is important to ignore SIGCHLD before the child process exits though. For instance, call signal before you call execute as in the example above.

{
    child c = execute(run_exe("test.exe"));
}

On Windows child is a movable but non-copyable type. The destructor closes handles to the child process when the instance of child goes out of scope. On Windows it's not strictly required to call wait_for_exit to clean up resources.

Boost.Process provides two initializers to detect errors. Use set_on_error if you want execute to return an error:

boost::system::error_code ec;
execute(
    run_exe("test.exe"),
    set_on_error(ec)
);

Use throw_on_error if you want execute to throw an exception:

execute(
    run_exe("test.exe"),
    throw_on_error()
);

The type of the exception thrown by throw_on_error is boost::system::system_error.

[Note] Note

On POSIX set_on_error and throw_on_error detect a failed call to fork and execve. If execve fails the initializers send errno through a pipe from the child to the parent process. The pipe is automatically closed no matter whether execve succeeds or fails.

Use the initializer set_args to set command line arguments:

std::vector<std::string> args;
args.push_back("test.exe");
args.push_back("--foo");
args.push_back("/bar");

execute(set_args(args));

If set_args is used, run_exe may be omitted. The first command line argument must then refer to the program to start.

Alternatively use the initializer set_cmd_line to set the command line:

execute(
    run_exe("test.exe"),
    set_cmd_line("test --foo /bar")
);

You can use set_cmd_line to set a command line like you would if you started the program in the shell. Just like in the shell you must start the command line with the name of the program.

You must not omit run_exe if you use set_cmd_line.

[Note] Note

If you don't use set_args or set_cmd_line the path you pass to run_exe is forwarded as the only argument to a program. That is the argument which is accessible through argv[0] (assuming argv is the name of the parameter in main).

Use the initializer start_in_dir to set the work directory:

execute(
    run_exe("test.exe"),
    start_in_dir("../foo")
);

start_in_dir also supports boost::filesystem::path.

For portability reasons you want to use an absolute path with run_exe if you set the work directory with start_in_dir:

boost::filesystem::path exe = "test.exe";
execute(
    run_exe(boost::filesystem::absolute(exe)),
    start_in_dir("../foo")
);

On Windows a relative path is relative to the work directory of the parent process. On POSIX a relative path is relative to the work directory set with start_in_dir as the directory is changed before the program starts.

[Tip] Tip

Use an absolute path with run_exe if you set the work directory with start_in_dir to avoid portability problems.

Boost.Process provides the initializer inherit_env to inherit environment variables:

execute(
    run_exe("test.exe"),
    inherit_env()
);

While inherit_env is required on POSIX, environment variables are also inherited without this initializer on Windows as on Windows environment variables are inherited by default.

If you want to set environment variables for the child process explicitly, use the initializer set_env.

Boost.Process provides the initializers bind_stdin, bind_stdout and bind_stderr to setup standard streams. They are based on the classes boost::iostreams::file_descriptor_source and boost::iostreams::file_descriptor_sink from Boost.Iostreams:

file_descriptor_sink sink("stdout.txt");
execute(
    run_exe("test.exe"),
    bind_stdout(sink)
);

In the example above the standard output stream of the child process is bound to a file. All data written by the child process to the standard output stream is written to stdout.txt. Have a look at the Boost.Iostreams documentation to find out what other devices you can use with boost::iostreams::file_descriptor_source and boost::iostreams::file_descriptor_sink.

[Note] Note

If you don't bind a stream with one of the initializers it depends on the platform where the streams are bound to. On Windows the streams are bound to the console by default. On POSIX the streams are inherited from the parent process and are bound to whatever they are bound to in the parent process.

If you want to close streams for a child process, use close_stdin, close_stdout and close_stderr:

execute(
    run_exe("test.exe"),
    bind_stdout(sink),
    close_stdin(),
    close_stderr()
);

For POSIX Boost.Process also provides close_fd to close a single file descriptor, close_fds to close multiple file descriptors and close_fds_if to close all file descriptors a predicate returns true for.

Boost.Process provides the function create_pipe to create an anonymous pipe. A parent and child process can use the pipe to send and receive data:

boost::process::pipe p = create_pipe();

file_descriptor_sink sink(p.sink, close_handle);
execute(
    run_exe("test.exe"),
    bind_stdout(sink)
);

file_descriptor_source source(p.source, close_handle);
stream<file_descriptor_source> is(source);
std::string s;
std::getline(is, s);

In the example above the standard output stream of the child process is bound to the write-end of the pipe. The read-end is used by the parent process to receive data. The class boost::iostreams::stream is another class provided by Boost.Iostreams to wrap file descriptor sources or sinks.

[Note] Note

There is a Boost.Iostreams bug on Windows in all versions up to 1.50.0. If you read from a boost::iostreams::file_descriptor_source which has been initialized with the read-end of a pipe, and the write-end of the pipe has been closed, an exception is thrown.

With the help of Boost.Asio it's possible to send and receive data between parent and child processes asynchronously. On Windows a named pipe must be used though as Windows doesn't support asynchronous I/O with anonymous pipes. That's why create_pipe can't be used for asynchronous I/O - at least not on Windows. The following example expects that a function called create_async_pipe has been defined which returns a pipe on all platforms supporting asynchronous I/O:

    boost::process::pipe p = create_async_pipe();

    file_descriptor_sink sink(p.sink, close_handle);
    execute(
        run_exe("test.exe"),
        bind_stdout(sink)
    );

    file_descriptor_source source(p.source, close_handle);

#if defined(BOOST_WINDOWS_API)
    typedef boost::asio::windows::stream_handle pipe_end;
#elif defined(BOOST_POSIX_API)
    typedef boost::asio::posix::stream_descriptor pipe_end;
#endif

    boost::asio::io_service io_service;
    pipe_end pend(io_service, p.source);

    boost::array<char, 4096> buffer;
    boost::asio::async_read(pend, boost::asio::buffer(buffer),
        [](const boost::system::error_code&, std::size_t){});

    io_service.run();

For asynchronous I/O Boost.Asio must be used. Boost.Asio provides the I/O objects boost::asio::windows::stream_handle on Windows and boost::asio::posix::stream_descriptor on POSIX which can be initialized with a read- or write-end of a pipe. Once the I/O objects have been created it's possible to use the asynchronous operations Boost.Asio provides.

[Note] Note

boost/process/mitigate.hpp provides a typedef boost::process::pipe_end for the two Boost.Asio types.

[Note] Note

There is a Boost.Iostreams bug on Windows in all versions up to 1.50.0. If you read from a boost::iostreams::file_descriptor_source which has been initialized with the read-end of a pipe, and the write-end of the pipe has been closed, an exception is thrown.

[Note] Note

Please note that create_async_pipe is not provided by Boost.Process. First, the concept of an asynchronous pipe is artificial and only introduced for Boost.Process. Platforms distinguish between anonymous and named pipes. Secondly, there are too many options to define a named pipe - that's the only pipe supporting asynchronous I/O on Windows - that it's not an easy exercise to create a platform-independent create_named_pipe function.

Call wait_for_exit to wait for a program to exit:

child c = execute(run_exe("test.exe"));
auto exit_code = wait_for_exit(c);

On Windows wait_for_exit returns the exit code of the program as a DWORD. On POSIX the function returns the status of the program as an int. DWORD is defined as unsigned long. While both are numeric integer types, you must use the macro WEXITSTATUS to get the exit code on POSIX.

[Note] Note

boost/process/mitigate.hpp defines a macro BOOST_PROCESS_EXITSTATUS which works like WEXITSTATUS on POSIX and casts to int on Windows. You can use this macro to get the exit code as an int on all platforms.

wait_for_exit is a blocking function. If you want to wait asynchronously, use Boost.Process together with Boost.Asio:

    boost::asio::io_service io_service;

#if defined(BOOST_POSIX_API)
    int status;
    boost::asio::signal_set set(io_service, SIGCHLD);
    set.async_wait(
        [&status](const boost::system::error_code&, int) { ::wait(&status); }
    );
#endif

    child c = execute(
        run_exe("test.exe")
#if defined(BOOST_POSIX_API)
        , notify_io_service(io_service)
#endif
    );

#if defined(BOOST_WINDOWS_API)
    DWORD exit_code;
    boost::asio::windows::object_handle handle(io_service, c.process_handle());
    handle.async_wait(
        [&handle, &exit_code](const boost::system::error_code&)
            { ::GetExitCodeProcess(handle.native(), &exit_code); }
    );
#endif

    io_service.run();

Call terminate to close a program:

child c = execute(run_exe("test.exe"));
terminate(c);

terminate closes a program immediately and forcefully. It is a last resort function as it doesn't give the program to be closed any chance to clean up resources. For instance, if the program is in the middle of writing data to a file, terminate can leave that data in an undefined state.


PrevUpHomeNext