Home | Libraries | People | FAQ | More |
The smallest program to start a child consists of specifying the executable name and the arguments, setting up the context and calling a free-standing function to launch the process.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> namespace bp = ::boost::process; int main() { std::string exec = "bjam"; std::vector<std::string> args; args.push_back("--version"); bp::context ctx; ctx.stdout_behavior = bp::silence_stream(); bp::child c = bp::launch(exec, args, ctx); }
The header file boost/process.hpp
can be used to declare easily all classes and functions which are defined by Boost.Process. It simply includes all header files. Instead of including boost/process.hpp
the program would also compile if the header files boost/process/context.hpp
, boost/process/stream_behavior.hpp
, boost/process/operations.hpp
and boost/process/child.hpp
would have been included.
In order to start a process the executable name and the arguments must be specified and a context must be setup. While the executable name is simply a std::string
and arguments are stored in a std::vector<std::string>
the context is a class from Boost.Process. It provides three properties stdin_behavior
, stdout_behavior
and stderr_behavior
to specify the behavior of the stdin, stdout and stderr streams. By default all streams are closed. If a child should be able to read from or write to some of these streams they need to be configured. Boost.Process provides a few helper functions - among others silence_stream
. With silence_stream
a stream is not closed but all data written to it is discarded. For the program above it means that bjam can print the version number as the stdout stream is available. However the version number is written to a null device.
The free-standing function launch
returns a child
object. If a parent does not want to communicate with a child nor wait for its termination it can simply forget about it. When the child
object goes out of scope and is destroyed the parent can not reach the child anymore. However the child may still be running and even might outlive the parent.
When a child has been started wait
can be called to wait for the child to terminate.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> namespace bp = ::boost::process; bp::child start_child() { std::string exec = "bjam"; std::vector<std::string> args; args.push_back("--version"); bp::context ctx; ctx.stdout_behavior = bp::silence_stream(); return bp::launch(exec, args, ctx); } int main() { bp::child c = start_child(); bp::status s = c.wait(); return s.exited() ? s.exit_status() : EXIT_FAILURE; }
wait
returns a status
object when the child has terminated. As long as the child is running wait
does not return but blocks.
The returned status
object can be used to check the exit code of the child via exit_status
. Before exit_status
is called though the return value of exited
should be checked to make sure the child exited gracefully. Only if exited
returns true
exit_status
may be called.
Boost.Process can not only be used to start a child and wait for its termination. It also enables a parent to communicate with a child via standard C++ streams.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process; bp::child start_child() { std::string exec = "bjam"; std::vector<std::string> args; args.push_back("--version"); bp::context ctx; ctx.stdout_behavior = bp::capture_stream(); return bp::launch(exec, args, ctx); } int main() { bp::child c = start_child(); bp::pistream &is = c.get_stdout(); std::string line; while (std::getline(is, line)) std::cout << line << std::endl; }
When a child has been started the stdin, stdout and stderr streams can be fetched by calling get_stdin
, get_stdout
and get_stderr
. As all streams are closed by default it's required to change the behavior of a stream prior to starting a child! If for example the parent wants to read what the child writes to stdout the behavior of the stdout stream must be changed to capture_stream
. Only then the parent can access the stdout stream of the child.
While stdout is a stream the child writes data to from the parent's point of view it's an input stream. That's why get_stdout
returns a pistream
object. As pistream
is derived from std::istream
it behaves like any other standard C++ stream. The only additional method available in pistream
is close
which can be called to close a stream and notify the child that the parent does not want to read any more data.
As pistream
behaves like a standard C++ stream all method calls can block. If the stream buffer is empty and the child doesn't write any data to the stream currently the parent will wait until there is new data written to the stream buffer. If it's not acceptable to block asynchronous I/O should be used.
While Boost.Process does not support aysynchronous I/O out of the box it provides methods to access the underlying handles of a stream which can be assigned to Asio objects.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #if defined(__CYGWIN__) # define _WIN32_WINNT 0x0501 # define __USE_W32_SOCKETS # undef BOOST_POSIX_API # define BOOST_WINDOWS_API #endif #include <boost/asio.hpp> #define BOOST_PROCESS_WINDOWS_USE_NAMED_PIPE #include <boost/process.hpp> #include <boost/array.hpp> #include <boost/bind.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process; namespace ba = ::boost::asio; ba::io_service io_service; boost::array<char, 4096> buffer; #if defined(BOOST_POSIX_API) ba::posix::stream_descriptor in(io_service); #elif defined(BOOST_WINDOWS_API) ba::windows::stream_handle in(io_service); #else # error "Unsupported platform." #endif bp::child start_child() { std::string exec = "bjam"; std::vector<std::string> args; args.push_back("--version"); bp::context ctx; ctx.stdout_behavior = bp::capture_stream(); return bp::launch(exec, args, ctx); } void end_read(const boost::system::error_code &ec, std::size_t bytes_transferred); void begin_read() { in.async_read_some(boost::asio::buffer(buffer), boost::bind(&end_read, ba::placeholders::error, ba::placeholders::bytes_transferred)); } void end_read(const boost::system::error_code &ec, std::size_t bytes_transferred) { if (!ec) { std::cout << std::string(buffer.data(), bytes_transferred) << std::flush; begin_read(); } } int main() { bp::child c = start_child(); bp::pistream &is = c.get_stdout(); in.assign(is.handle().release()); begin_read(); c.wait(); }
Asio provides the classes stream_descriptor
and stream_handle
to use file descriptors on POSIX platforms and HANDLEs on Windows for asynchronous I/O. By calling handle
an object is returned with owns the underlying handle. In order to access the underlying handle and assign it to a Asio object release
must be called. Ownership of the underlying handle will be transferred to the caller which means the streams won't be closed when the child
object goes out of scope.
Please note that the macro BOOST_PROCESS_WINDOWS_USE_NAMED_PIPE must be defined for asynchronous I/O to work on Windows platforms. By default streams use anonymous pipes which don't support asynchronous I/O on Windows though. By defining BOOST_PROCESS_WINDOWS_USE_NAMED_PIPE named pipes are used on Windows which support asynchronous I/O.
Once the underlying handles have been assigned to Asio objects asynchronous I/O works like usual. To make sure that the parent waits for the child to write data and does not terminate wait
is called.
When a context is setup the environment table which contains environment variables for the new process can be configured. By default the environment table is empty.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process; bp::child start_child(bp::context ctx) { #if defined(BOOST_POSIX_API) return bp::launch(std::string("env"), std::vector<std::string>(), ctx); #elif defined(BOOST_WINDOWS_API) return bp::launch_shell("set", ctx); #else # error "Unsupported platform." #endif } int main() { bp::context ctx; ctx.stdout_behavior = bp::capture_stream(); bp::child c = start_child(ctx); bp::pistream &is = c.get_stdout(); std::string line; while (std::getline(is, line)) std::cout << line << std::endl; }
The child started simply writes the environment variables to the stdout stream. As different commands have to be used on POSIX and Windows platforms the macros BOOST_POSIX_API and BOOST_WINDOWS_API are used. They are automatically available when boost/process.hpp
is included.
On POSIX platforms the program env is used to print environment variables. On Windows a built-in shell command has to be used. As the built-in shell command is not a stand-alone executable the free-standing function launch_shell
must be called. This function starts the shell (cmd.exe on Windows) and passes the command as an argument to the shell.
When the program is run which reads the data written to stdout by the child no data is received: By default the environment table is empty.
If a child should see the same environment variables as the parent process the context has to be setup accordingly.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process; bp::child start_child(bp::context ctx) { #if defined(BOOST_POSIX_API) return bp::launch(std::string("env"), std::vector<std::string>(), ctx); #elif defined(BOOST_WINDOWS_API) return bp::launch_shell("set", ctx); #else # error "Unsupported platform." #endif } int main() { bp::context ctx; ctx.stdout_behavior = bp::capture_stream(); ctx.environment = bp::self::get_environment(); bp::child c = start_child(ctx); bp::pistream &is = c.get_stdout(); std::string line; while (std::getline(is, line)) std::cout << line << std::endl; }
By calling current_environment
the current environment can be copied to the property environment
of a context. When the program is run the very same environment variables as seen by the parent are printed.
If a child should not see some environment variables or needs other environment variables to be defined the environment table can be modified.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> #include <iostream> namespace bp = ::boost::process; bp::child start_child(bp::context ctx) { #if defined(BOOST_POSIX_API) return bp::launch(std::string("env"), std::vector<std::string>(), ctx); #elif defined(BOOST_WINDOWS_API) return bp::launch_shell("set", ctx); #else # error "Unsupported platform." #endif } int main() { bp::context ctx; ctx.stdout_behavior = bp::capture_stream(); ctx.environment = bp::self::get_environment(); ctx.environment.erase("TMP"); ctx.environment.insert(bp::environment::value_type("MYNEWVAR", "VALUE")); bp::child c = start_child(ctx); bp::pistream &is = c.get_stdout(); std::string line; while (std::getline(is, line)) std::cout << line << std::endl; }
The property environment
of the context is a typedef for std::map<std::string, std::string>
. Thus it's easy to modify the environment table and remove or insert environment variables.
While status
is typically used to check the exit status of a terminated child on POSIX platforms the class posix_status
provides more methods to find out why and how a child terminated.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #if defined(BOOST_POSIX_API) #include <string> #include <vector> #include <iostream> #include <cstdlib> namespace bp = ::boost::process; bp::child start_child(std::string exec) { std::vector<std::string> args; bp::context ctx; ctx.stdin_behavior = bp::inherit_stream(); ctx.stdout_behavior = bp::inherit_stream(); ctx.stderr_behavior = bp::inherit_stream(); return bp::launch(exec, args, ctx); } int main(int argc, char *argv[]) { if (argc < 2) { std::cerr << "Please provide a program name" << std::endl; return EXIT_FAILURE; } bp::child c = start_child(argv[1]); bp::posix_status s = c.wait(); if (s.exited()) std::cout << "Program returned exit code " << s.exit_status() << std::endl; else if (s.signaled()) { std::cout << "Program received signal " << s.term_signal() << std::endl; if (s.dumped_core()) std::cout << "Program also dumped core" << std::endl; } else if (s.stopped()) std::cout << "Program stopped by signal " << s.stop_signal() << std::endl; else std::cout << "Unknown termination reason" << std::endl; return s.exited() ? s.exit_status() : EXIT_FAILURE; } #endif
The methods signaled
and stopped
can be called to check if the process exited due to an external signal or was stopped by an external signal. If they return true
additional methods can be called to get the signal code.
On POSIX platforms it's possible to setup more communication streams than stdin, stdout and stderr.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #if defined(BOOST_POSIX_API) #include <string> #include <vector> #include <iostream> #include <cstdlib> #include <unistd.h> namespace bp = ::boost::process; bp::posix_child start_child() { std::string exec = bp::find_executable_in_path("dbus-daemon"); std::vector<std::string> args; args.push_back("--fork"); args.push_back("--session"); args.push_back("--print-address=3"); args.push_back("--print-pid=4"); bp::posix_context ctx; ctx.output_behavior.insert(bp::behavior_map::value_type(STDOUT_FILENO, bp::inherit_stream())); ctx.output_behavior.insert(bp::behavior_map::value_type(STDERR_FILENO, bp::inherit_stream())); ctx.output_behavior.insert(bp::behavior_map::value_type(3, bp::capture_stream())); ctx.output_behavior.insert(bp::behavior_map::value_type(4, bp::capture_stream())); return bp::posix_launch(exec, args, ctx); } int main() { try { bp::posix_child c = start_child(); std::string address; pid_t pid; c.get_output(3) >> address; c.get_output(4) >> pid; bp::status s = c.wait(); if (s.exited()) { if (s.exit_status() == EXIT_SUCCESS) { std::cout << "D-BUS daemon's address is: " << address << std::endl; std::cout << "D-BUS daemon's PID is: " << pid << std::endl; } else std::cout << "D-BUS daemon returned error condition: " << s.exit_status() << std::endl; } else std::cout << "D-BUS daemon terminated abnormally" << std::endl; return s.exited() ? s.exit_status() : EXIT_FAILURE; } catch (boost::filesystem::filesystem_error &ex) { std::cout << ex.what() << std::endl; return EXIT_FAILURE; } } #endif
The class posix_context
provides the properties input_behavior
and output_behavior
to add and configure more streams than stdin, stdout and stderr. When started those streams can be accessed by calling get_intput
and get_output
on posix_child
.
With win32_context
it's possible to add a STARTUPINFO structure which will be used by win32_launch
to start a Windows process.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #if defined(BOOST_WINDOWS_API) #include <string> #include <vector> #include <iostream> #include <windows.h> namespace bp = ::boost::process; bp::win32_child start_child() { std::string exec = bp::find_executable_in_path("notepad"); std::vector<std::string> args; STARTUPINFO si; ::ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); si.dwFlags |= STARTF_USEPOSITION | STARTF_USESIZE; si.dwX = 0; si.dwY = 0; si.dwXSize = 640; si.dwYSize = 480; bp::win32_context ctx; ctx.startupinfo = &si; return bp::win32_launch(exec, args, ctx); } int main() { try { bp::win32_child c = start_child(); } catch (boost::filesystem::filesystem_error &ex) { std::cout << ex.what() << std::endl; } } #endif
The STARTUPINFO structure enables developers to specify for example location and size of the main window of the new process.
If it's required to access process and thread identifiers and handles on Windows platforms a child should be started with win32_launch
.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #if defined(BOOST_WINDOWS_API) #include <string> #include <vector> #include <iostream> #include <windows.h> namespace bp = ::boost::process; bp::win32_child start_child() { std::string exec = bp::find_executable_in_path("notepad"); std::vector<std::string> args; bp::win32_context ctx; return bp::win32_launch(exec, args, ctx); } int main() { try { bp::win32_child c = start_child(); std::cout << "Process handle : 0x" << c.get_handle() << std::endl; std::cout << "Process identifier : " << c.get_id() << std::endl; std::cout << "Primary thread handle : 0x" << c.get_primary_thread_handle() << std::endl; std::cout << "Primary thread identifier : " << c.get_primary_thread_id() << std::endl; } catch (boost::filesystem::filesystem_error &ex) { std::cout << ex.what() << std::endl; } } #endif
The function win32_launch
returns a win32_child
object which provides additional methods to access identifiers and handles of the child's process and primary thread.
The library supports creating a so-called pipeline: Children are spawned in a way that an output stream is connected to the input stream of the next child.
// // Boost.Process // ~~~~~~~~~~~~~ // // Copyright (c) 2006, 2007 Julio M. Merino Vidal // Copyright (c) 2008 Boris Schaeling // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include <boost/process.hpp> #include <string> #include <vector> #include <iostream> #include <fstream> #include <cstdlib> namespace bp = ::boost::process; bp::children start_children() { bp::context ctxin; ctxin.stdin_behavior = bp::capture_stream(); bp::context ctxout; ctxout.stdout_behavior = bp::inherit_stream(); ctxout.stderr_behavior = bp::redirect_stream_to_stdout(); std::string exec1 = bp::find_executable_in_path("cut"); std::vector<std::string> args1; args1.push_back("-d "); args1.push_back("-f2-5"); std::string exec2 = bp::find_executable_in_path("sed"); std::vector<std::string> args2; args2.push_back("s,^,line: >>>,"); std::string exec3 = bp::find_executable_in_path("sed"); std::vector<std::string> args3; args3.push_back("s,$,<<<,"); std::vector<bp::pipeline_entry> entries; entries.push_back(bp::pipeline_entry(exec1, args1, ctxin)); entries.push_back(bp::pipeline_entry(exec2, args2, ctxout)); entries.push_back(bp::pipeline_entry(exec3, args3, ctxout)); return bp::launch_pipeline(entries); } int main(int argc, char *argv[]) { try { if (argc < 2) { std::cerr << "Please specify a file name" << std::endl; return EXIT_FAILURE; } std::ifstream file(argv[1]); if (!file) { std::cerr << "Cannot open file" << std::endl; return EXIT_FAILURE; } bp::children cs = start_children(); bp::postream &os = cs.front().get_stdin(); std::string line; while (std::getline(file, line)) os << line << std::endl; os.close(); bp::status s = bp::wait_children(cs); return s.exited() ? s.exit_status() : EXIT_FAILURE; } catch (boost::filesystem::filesystem_error &ex) { std::cout << ex.what() << std::endl; return EXIT_FAILURE; } }