注意 | |
---|---|
Process v1 将在下一个版本 (1.88) 中弃用。新项目请使用 v2。 |
Boost.Process 是一个用于管理系统进程的库。它可以用于
以下是如何使用 Boost.Process 启动程序的简单示例
#include <boost/process.hpp
> #include <string> #include <iostream> using namespace boost::process; int main() {ipstream
pipe_stream;child
c("gcc --version",std_out
> pipe_stream); std::string line; while (pipe_stream && std::getline(pipe_stream, line) && !line.empty()) std::cerr << line << std::endl; c.wait(); }
本节将解释此库中使用的操作系统的一些底层概念。在接下来的章节中,我们将假设您已了解这些概念。但请注意,这只是一个简短的总结,并非涵盖所有内容。
此库的目标是实现一个可移植的包装器,因此我们将主要解释 Windows 和 POSIX 的共同之处。
管道是操作系统提供的用于不同线程、进程以及某些情况下机器之间通信的工具。
管道的典型特征是它是一个单通道,提供两个句柄,一个用于读取(源),一个用于写入(接收器)。这与其他工具(如套接字)不同,并提供另一种管理连接的方式:如果管道的一端关闭(即管道中断),则另一端会收到通知。
管道通常用于进程间通信。主要原因是管道可以直接分配给进程的标准输入输出,即 stderr、stdin 和 stdout。此外,管道的一半可以继承到子进程,并在父进程中关闭。这将导致子进程退出时管道中断。
但请注意,如果同一线程读取和写入管道,它只会与自身通信。
进程是一个独立的可执行实体,它与线程不同,因为它拥有自己的资源。这些资源包括内存和硬件资源。
每个进程都由一个唯一的数字[27]标识,称为进程标识符,pid
。
进程将返回一个整数值,指示其是否成功。在 POSIX 中,与此相关的代码更多,但在 Windows 中则不是这样。因此,库中目前没有此类编码。但是,退出代码为零表示进程成功,而不同于零的值表示错误。
也可以强制进程退出。有两种方法可以做到这一点:发出信号让进程退出并等待,以及直接终止进程而无需任何条件。
通常,第一种方法是发出退出请求,但 Windows 与 POSIX 不同,它没有提供一致的方法来执行此操作。因此,这并非库的一部分,只有强制终止是。
环境是每个进程的局部变量映射。对于此库而言,最重要的变量是 PATH
变量,它包含应搜索可执行文件的路径列表。shell 会自动执行此操作,而此库为此提供了一个函数。
在本节中,我们将逐步介绍 boost.process 的不同功能。有关完整说明,请参阅 参考 和 概念 部分。
我们想启动一个进程,所以让我们从一个简单的进程开始。我们将调用 gcc 编译器来编译一个简单的程序。
使用标准库,它看起来像这样。
int result = std::system("g++ main.cpp");
我们可以在 boost.process 中这样编写。
namespace bp = boost::process; //we will assume this for all further examples
int result = bp::system
("g++ main.cpp");
如果给定单个字符串(或显式形式 bp::cmd
),它将被解释为命令行。这将导致执行函数搜索 PATH
变量以查找可执行文件。另一种方法是 exe-args
样式,其中第一个字符串将被解释为文件名(包括路径),其余部分将作为传递给该函数的参数。
注意 | |
---|---|
有关 |
因此,第一步,我们将使用 exe-args
样式。
int result = bp::system
("/usr/bin/g++", "main.cpp");
使用该语法,我们仍然将“g++”硬编码,因此让我们假设我们从外部源获取字符串作为 boost::process::v1::filesystem::path
,我们也可以这样做。
boost::process::v1::filesystem::path p = "/usr/bin/g++"; //or get it from somewhere else.
int result = bp::system
(p, "main.cpp");
现在我们可能想要在 PATH
变量中查找 g++
可执行文件,就像 cmd
语法那样。Boost.process
提供了一个为此目的的函数:bp::search_path
。
boost::process::v1::filesystem::path p =bp::search_path
("g++"); //or get it from somewhere else. int result =bp::system
(p, "main.cpp");
注意 | |
---|---|
|
鉴于我们的示例使用了 system
函数,我们的程序将等到子进程完成。这可能是我们不希望的,特别是编译可能需要一段时间。
为了避免这种情况,boost.process 提供了几种启动进程的方法。除了前面提到的 system
函数及其异步版本 async_system
之外,我们还可以使用 spawn
函数或 child
类。
spawn
函数启动一个进程并立即分离它,因此不会返回句柄,并且会忽略该进程。这不是我们编译所需的,但也许我们想在编译时娱乐用户
bp::spawn
(bp::search_path
("chrome"), "www.boost.org");
现在让我们来看一下更合理的编译方法:非阻塞执行。为此,我们直接调用 child
的构造函数。
bp::child
c(bp::search_path
("g++"), "main.cpp"); while (c.running
()) do_some_stuff(); c.wait
(); //wait for the process to exit int result = c.exit_code
();
我们通过调用子构造函数来启动进程。然后,在进程运行期间和运行之后,我们检查并执行其他操作,并获取退出代码。调用wait
是必要的,以便获取退出代码并告诉操作系统,没有人再等待该进程了。
注意 | |
---|---|
您还可以使用 |
警告 | |
---|---|
如果您不调用子对象上的 wait,则它会在销毁时被终止。这可以通过事先调用 |
到目前为止,我们假设一切都能正常运行,但“g++”可能不存在,这将导致进程启动失败。所有函数的默认行为是在失败时抛出std::system_error。与该库中的许多其他函数一样,传递std::error_code 将改变行为,这样就不会抛出异常,而是将错误赋值给错误代码。
std::error_code ec;
bp::system
("g++ main.cpp", ec);
在上面给出的示例中,我们只启动了一个程序,但没有考虑输出。默认值取决于系统,但通常情况下,这只会将输出写入与启动进程相同的输出。如果需要保证这一点,可以像这样显式地转发流。
bp::system
("g++ main.cpp",bp::std_out
> stdout,bp::std_err
> stderr,bp::std_in
< stdin);
现在对于第一个示例,我们可能只想忽略输出,这可以通过将其重定向到空设备来实现。可以这样实现:
bp::system
("g++ main.cpp",bp::std_out
>bp::null
);
或者,我们也可以轻松地将输出重定向到文件。
bp::system
("g++ main.cpp",bp::std_out
> "gcc_out.log");
现在,让我们来看一个更直观的读取数据的示例。nm 是 posix 上的一个工具,它读取二进制文件的概要,即所有入口点的列表。每个入口点都将放在一行中,我们将使用管道来读取它。最后附加一个空行,我们用它作为停止读取的指示。Boost.process 提供了 pipestream(ipstream
、opstream
、pstream
)来包装pipe
并提供std::istream、std::ostream 和std::iostream 接口的实现。
std::vector<std::string> read_outline(std::string & file) {bp::ipstream
is; //reading pipe-streambp::child
c(bp::search_path
("nm"), file,bp::std_out
> is); std::vector<std::string> data; std::string line; while (c.running
() && std::getline(is, line) && !line.empty()) data.push_back(line); c.wait
(); return data; }
这样做会将进程的stdout
重定向到一个管道中,我们同步地读取它。
注意 | |
---|---|
您可以对 |
现在我们从nm
获取名称,我们可能想要对其进行反混淆,因此我们使用输入和输出。nm
有一个反混淆选项,但为了举例说明,我们将为此使用c++filt。
bp::opstream
in;bp::ipstream
out;bp::child
c("c++filt", std_out > out, std_in < in); in << "_ZN5boost7process8tutorialE" << endl; std::string value; out >> value; c.terminate
();
现在您可能想要将一个进程的输出转发到另一个进程的输入。
std::vector<std::string> read_demangled_outline(const std::string & file) {bp::pipe
p;bp::ipstream
is; std::vector<std::string> outline; //we just use the same pipe, so the output of nm is directly passed as input to c++filtbp::child
nm(bp::search_path
("nm"), file,bp::std_out
> p);bp::child
filt(bp::search_path
("c++filt"),bp::std_in
< p,bp::std_out
> is); std::string line; while (filt.running() && std::getline(is, line)) //when nm finished the pipe closes and c++filt exits outline.push_back(line); nm.wait
(); filt.wait(); }
这将nm
的数据转发到c++filt
,而无需您的进程执行任何操作。
Boost.process 允许使用 boost.asio 来实现异步 I/O。如果您熟悉boost.asio(我们强烈推荐),您可以使用async_pipe
,它作为 I/O 对象实现,可以像上面显示的pipe
一样使用。
现在我们回到我们的编译示例。对于nm
,我们可能会逐行分析输出,但编译器输出将只放入一个大型缓冲区中。
使用boost.asio,它看起来是这样的。
boost::asio::io_service ios; std::vector<char> buf(4096);bp::async_pipe
ap(ios);bp::child
c(bp::search_path
("g++"), "main.cpp",bp::std_out
> ap); boost::asio::async_read(ap, boost::asio::buffer(buf), [](const boost::system::error_code &ec, std::size_t size){}); ios.run(); int result = c.exit_code();
为了简化操作,Boost.process 提供了一个更简单的接口,这样就可以直接传递缓冲区,前提是我们还传递对boost::asio::io_service 的引用。
boost::asio::io_service ios; std::vector<char> buf(4096);bp::child
c(bp::search_path
("g++"), "main.cpp",bp::std_out
> boost::asio::buffer(buf), ios); ios.run(); int result = c.exit_code();
注意 | |
---|---|
将boost::asio::io_service 的实例传递给启动函数会自动使其异步等待退出,因此无需调用 |
为了更方便,您可以使用std::future 进行异步操作(您仍然需要将对boost::asio::io_service 的引用传递给启动函数),除非您使用bp::system
或bp::async_system
。
现在我们将重新审视我们的第一个示例,并异步读取编译器输出。
boost::asio::boost::asio::io_service ios; std::future<std::string> data; child c("g++", "main.cpp", //set the inputbp::std_in
.close(),bp::std_out
>bp::null
, //so it can be written without anythingbp::std_err
> data, ios); ios.run(); //this will actually block until the compiler is finished auto err = data.get();
启动多个进程时,可以将它们组合在一起。如果子进程不修改组成员身份,这也适用于启动其他进程的子进程。例如,如果您调用make
(它启动其他进程)并在其上调用 terminate,除非您使用组,否则它不会终止所有子进程。
使用组的两个主要原因是:
如果我们有一个像make
这样的程序,它会启动它自己的子进程,调用terminate
可能不够。例如,如果我们有一个启动gcc
的 makefile 并使用以下代码,gcc
进程仍将在之后运行。
bp::child
c("make"); if (!c.wait_for
(std::chrono::seconds(10))) //give it 10 seconds c.terminate
(); //then terminate
因此,为了终止gcc
,我们可以使用组。
bp::group
g;bp::child
c("make", g); if (!g.wait_for
(std::chrono::seconds(10))) g.terminate
(); c.wait
(); //to avoid a zombie process & get the exit code
在给定的示例中,我们仍然调用wait
以避免僵尸进程。一个更简单的解决方案可能是使用spawn
。
要将两个进程放入一个组中,以下代码就足够了。Spawn 已经启动了一个分离的进程(即没有子句柄),但它们可以分组,以便在出现问题时,RAII 仍然是给定的。
void f() {bp::group
g;bp::spawn
("foo", g);bp::spawn
("bar", g); do_something(); g.wait
(); };
在示例中,它将在函数结束时等待这两个进程,除非发生异常。即,如果抛出异常,则该组将被终止。
有关更多信息,请参阅boost/process/group.hpp
。
此库提供对当前进程环境的访问,并允许为子进程设置环境。
//get a handle to the current environment auto env =boost::this_process::environment
(); //add a variable to the current environment env["VALUE_1"] = "foo"; //copy it into an environment separate to the one of this processbp::environment
env_ = env; //append two values to a variable in the new env env_["VALUE_2"] += {"bar1", "bar2"}; //launch a process with `env_`bp::system
("stuff", env_);
修改子进程环境的一种更方便的方法是env
属性,它可以在示例中按如下方式使用:
bp::system
("stuff",bp::env
["VALUE_1"]="foo",bp::env
["VALUE_2"]+={"bar1", "bar2"});
有关更多信息,请参阅boost/process/environment.hpp
。
此库旨在为不同的操作系统特定方法提供一个包装器来启动进程。其目标是提供在这些系统上可用的所有功能,并允许用户执行所有需要使用操作系统 API 的相关操作。
此库不尝试为所有与进程相关的操作提供一个完整的库。 在许多讨论中,有人提议将 boost.process 构建成某种形式的 DSEL[28]。这不是目标,它更侧重于提供在它之上构建此类 DSEL 库的工具。因此,该库也不会强迫用户使用任何特定的方式(例如仅异步通信)。它可以与这样的库集成。
Boost.Process 在构建进程时使用了非常特殊的风格。这是因为进程包含许多属性,而这些属性并非实际子类的成员。在许多情况下,父进程无法访问这些属性,例如使用环境变量时。子进程可以修改自己的环境,但父进程无法知道这一点。这意味着子进程具有一些在 C++ 中无法访问的属性。
这就导致了该库支持和混合使用的两种风格:重载和属性。假设您可能想要启动一个进程并传递多个参数。这两种风格都支持这种方式,如下所示
system("gcc", "--version"); //overloading system("gcc", args={"--version"}); //property style.
在某些情况下,这两种风格也可以混合使用。
system("gcc", "-c", args+={"main.cpp"});
在下一节中,将描述可用的风格。请注意,重载风格是通过类型特征实现的,因此将列出这些类型。
警告 | |
---|---|
无法保证参数的应用顺序!但是,可以保证属于同一组的参数的顺序,即字符串参数和 args 属性将按给定的顺序进行评估。 |
向进程传递参数时,提供两种风格:cmd 风格和 exe-/args 风格。
cmd 风格将字符串解释为 exe 和参数的序列并按此进行解析,而 exe-/args 风格将每个字符串解释为一个参数。
使用重载变体时,单个字符串将导致 cmd 解释,多个字符串将产生 exe-args 解释。两种版本都可以显式设置。
system("grep -c false /etc/passwd"); //cmd style system("grep", "-c", "false", "/etc/passwd"); //exe-/args- system(cmd="grep -c false /etc/passwd"); //cmd style system(exe="grep", args={"-c", "false", "/etc/passwd"}); //exe-/args-
注意 | |
---|---|
如果在参数风格中使用 '"' 符号,它将作为参数的一部分传递。如果希望 cmd 语法产生相同的效果,则应进行转义,即 '\"'。 |
注意 | |
---|---|
在命令风格中,将自动搜索 |
扩展功能的最简单形式是提供另一个处理程序,该处理程序将在进程启动时的相应事件上被调用。名称如下:
boost::process::v1::on_setup
boost::process::v1::on_error
boost::process::v1::on_success
例如
child c("ls", on_setup([](){cout << "On Setup" << endl;}));
注意 | |
---|---|
在 POSIX 系统上,所有这些回调都将由该进程处理,而不是由创建的进程处理。这与 POSIX 扩展不同,POSIX 扩展可以在派生进程上执行。 |
要扩展库,请使用boost/process/extend.hpp
头文件。
它只提供自定义属性的显式风格,而不提供隐式风格。
这意味着可以实现自定义初始化程序,一个可以传递给启动函数之一的引用。如果一个类继承了boost::process::v1::extend::handler
,它将被视为初始化程序,并直接放入执行器接收的序列中。
最简单的扩展只接收单个处理程序,这可以用函数式风格来实现。因此,让我们从一个简单的 hello-world 示例开始,同时使用 C++14 通用 lambda 表达式。
using namespace boost::process; namespace ex = bp::extend;child
c("foo",ex::on_success
=[](auto & exec) {std::cout << "hello world" << std::endl;});
考虑到 lambda 表达式也可以捕获值,数据可以很容易地在处理程序之间共享。
要查看执行器有哪些成员,请参考windows_executor
和posix_executor
。
注意 | |
---|---|
结合 |
警告 | |
---|---|
POSIX 处理程序符号未在 Windows 上定义。 |
由于前面的示例采用函数式风格,因此它不太可重用。为了解决这个问题,handler
在boost::process::v1::extend
命名空间中有一个别名,可以继承它。因此,让我们用类来实现 hello world 示例。
struct hello_world :handler
{ template<typename Executor> voidex::on_success
(Executor & exec) const { std::cout << "hello world" << std::endl; } }; //in our functionchild
c("foo", hello_world());
注意 | |
---|---|
实现是通过重载而不是重写来完成的。 |
每个未实现的处理程序都默认为handler
,其中为每个事件定义了一个空处理程序。
由于boost.process
提供了对boost.asio的接口,因此扩展也可用此功能。如果类需要boost::asio::io_context,则可以使用以下代码。
struct async_foo :handler
,ex::require_io_context
{ template<typename Executor> void on_setup(Executor & exec) { boost::asio::io_context & ios =ex::get_io_context
(exec.seq); //gives us a reference and a compiler error if not present. //do something with ios } };
注意 | |
---|---|
继承 |
此外,处理程序可以提供一个在子进程退出时调用的函数。这是通过ex::async_handler
来完成的。
注意 | |
---|---|
|
struct async_bar : __handler, ex::async_handler
{
template<typename Executor>
std::function<void(int, const std::error_code&)> on_exit_handler(Executor & exec)
{
auto handler_ = this->handler;
return [handler_](int exit_code, const std::error_code & ec)
{
std::cout << "hello world, I exited with " << exit_code << std::endl;
};
}
};
警告 | |
---|---|
|
警告 | |
---|---|
|
如果初始化程序中发生错误,则应将其告知执行器,而不是直接处理。这是因为可以通过传递给启动函数的参数来更改行为。因此,执行器具有set_error
函数,该函数接受一个std::error_code和一个字符串。根据执行器的配置,这可能会抛出异常,设置内部error_code
,或者什么也不做。
因此,让我们来看一个简单的示例,我们设置一个随机选择的error_code
。
auto set_error = [](auto & exec)
{
std::error_code ec{42, std::system_category()};
exec.set_error(ec, "a fake error");
};
child
c("foo", on_setup=set_error);
由于我们在此示例中未指定错误处理模式,因此这将抛出process_error
。
现在我们有了自定义初始化程序,让我们考虑如何处理不同执行器之间的差异。区别在于 POSIX 和 Windows,以及 Windows 上的char
和wchar_t
。一种解决方案是使用BOOST_WINDOWS_API 和 BOOST_POSIX_API宏,这些宏在包含任何 process 头文件后都会自动可用。
另一个变体是类型别名ex::posix_executor
和ex::windows_executor
,其中当前系统以外的执行器是前向声明。这可以正常工作,因为该函数永远不会被调用。因此,让我们实现另一个示例,它打印可执行文件名ex::on_success
。
struct hello_exe :handler
{ template<typename Sequence> voidex::on_success
(ex::posix_executor
<Sequence> & exec) { std::cout << "posix-exe: " << exec.exe << std::endl; } template<typename Sequence> voidex::on_success
(ex::windows_executor
<char, Sequence> & exec) { //note: exe might be a nullptr on windows. if (exec.exe != nullptr) std::cout << "windows-exe: " << exec.exe << std::endl; else std::cout << "windows didn't use exe" << std::endl; } template<typename Sequence> voidex::on_success
(ex::windows_executor
<wchar_t, Sequence> & exec) { //note: exe might be a nullptr on windows. if (exec.exe != nullptr) std::wcout << L"windows-exe: " << exec.exe << std::endl; else std::cout << "windows didn't use exe" << std::endl; } };
因此,根据我们的示例,使用非本机执行器的定义仍然是一个模板,因此如果未使用,则不会对其进行评估。因此,这提供了一种在不使用预处理器的情况下实现系统特定代码的方法。
注意 | |
---|---|
如果您只编写部分实现,例如只针对 |
.
现在让我们重新审视我们的c++filt示例,我们将引入一个明显的错误。然而,对于更复杂的应用程序,这可能并不那么明显。
vector<string> demangle(vector<string> in) { ipstream is; opstream os; child c("c++filt", std_out > is, std_in < os); vector<string> data; for (auto & elem : data) { string line; getline(is, line); os << elem; } }
我们颠倒了读写操作,导致了死锁。这会立即锁定。这是因为c++filt
期望在输出任何数据之前输入数据。另一方面,启动进程等待其输出。
再举一个例子,这个例子看起来可能是正确的,假设您想使用ls
读取当前目录。
ipstream is; child c("ls", std_out > is); std::string file; while (is >> file) cout << "File: " << file << endl;
这也会导致死锁,因为子进程退出时管道不会关闭。ipstream
即使进程已结束,仍然会查找数据。
注意 | |
---|---|
在这个库中无法使用自动管道关闭,因为管道可能是一个文件句柄(例如 Windows 上的异步管道)。 |
但是,由于管道是缓冲的,如果您这样做,可能会得到不完整的数据。
ipstream is; child c("ls", std_out > is); std::string file; while (c.running()) { is >> file; cout << "File: " << file << endl; }
因此,如果您不确定输出是什么样的,强烈建议您使用异步 API。
由于 Windows 不使用 UTF-8,因此有时不可避免地要使用 WinApi 的wchar_t
版本。为了保持此库的一致性,它还在 posix 上提供了wchar_t
支持。
由于 posix api 纯粹是char
,所以每个基于wchar_t
的类型都将转换为char
。
另一方面,Windows 更具选择性;默认情况下使用char
,但如果任何参数需要wchar_t
,则所有内容都将转换为wchar_t
。这也包括boost::filesystem::path
。此外,如果系统不提供char
api(如 Windows CE 的情况),所有内容也将被转换。
namespace boost { namespace process { namespace v1 { class async_pipe; } } }
namespace boost { namespace process { namespace v1 { template<typename ExitHandler, typename ... Args> unspecified async_system(boost::asio::io_context &, ExitHandler &&, Args &&...); } } }
namespace boost { namespace process { namespace v1 { class child; typedef unspecified pid_t; // Typedef for the type of an pid_t. } } }
namespace boost { namespace process { namespace v1 { template<typename Char> class basic_environment; template<typename Char> class basic_native_environment; typedef basic_native_environment< char > native_environment; // Definition of the environment for the current process. typedef basic_native_environment< wchar_t > wnative_environment; // Definition of the environment for the current process. typedef basic_environment< char > environment; // Type definition to hold a seperate environment. typedef basic_environment< wchar_t > wenvironment; // Type definition to hold a seperate environment. } } namespace this_process { // Get the process id of the current process. int get_id(); // Get the native handle of the current process. native_handle_type native_handle(); // Get the enviroment of the current process. native_environment environment(); // Get the enviroment of the current process. wnative_environment wenvironment(); // Get the path environment variable of the current process runs. std::vector< boost::process::v1::filesystem::path > path(); } }
namespace boost { namespace process { namespace v1 { struct process_error; } } }
namespace boost { namespace process { namespace v1 { namespace extend { struct async_handler; struct handler; template<typename Sequence> struct posix_executor; struct require_io_context; template<typename Char, typename Sequence> struct windows_executor; unspecified on_setup; // This handler is invoked before the process in launched, to setup parameters. The required signature isvoid(Exec &)
, whereExec
is a template parameter. unspecified on_error; // This handler is invoked if an error occurred. The required signature isvoid(auto & exec, const std::error_code&)
, whereExec
is a template parameter. unspecified on_success; // This handler is invoked if launching the process has succeeded. The required signature isvoid(auto & exec)
, whereExec
is a template parameter. unspecified on_fork_error; // This handler is invoked if the fork failed. The required signature isvoid(auto & exec)
, whereExec
is a template parameter. unspecified on_exec_setup; // This handler is invoked if the fork succeeded. The required signature isvoid(Exec &)
, whereExec
is a template parameter. unspecified on_exec_error; // This handler is invoked if the exec call errored. The required signature isvoid(auto & exec)
, whereExec
is a template parameter. // Helper function to get the last error code system-independent. std::error_code get_last_error(); void throw_last_error(const std::string &); void throw_last_error(); template<typename Sequence> asio::io_context & get_io_context(const Sequence &); } } } }
namespace boost { namespace process { namespace v1 { class group; } } }
namespace boost { namespace process { namespace v1 { static unspecified limit_handles; } } namespace this_process { typedef unspecified native_handle_type; std::vector< native_handle_type > get_handles(); std::vector< native_handle_type > get_handles(std::error_code & ec); bool is_stream_handle(native_handle_type); bool is_stream_handle(native_handle_type handle, std::error_code & ec); } }
namespace boost { namespace process { namespace v1 { typedef std::codecvt< wchar_t, char, std::mbstate_t > codecvt_type; // The internally used type for code conversion. // Internally used error cateory for code conversion. const std::error_category & codecvt_category(); // Get a reference to the currently used code converter. const codecvt_type & codecvt(); // Set the locale of the library. std::locale imbue(const std::locale & loc); } } }
namespace boost { namespace process { namespace v1 { template<typename CharT, typename Traits = std::char_traits<CharT> > class basic_ipstream; template<typename CharT, typename Traits = std::char_traits<CharT> > class basic_opstream; template<typename CharT, typename Traits = std::char_traits<CharT> > class basic_pipe; template<typename CharT, typename Traits = std::char_traits<CharT> > struct basic_pipebuf; template<typename CharT, typename Traits = std::char_traits<CharT> > class basic_pstream; typedef basic_pipe< char > pipe; typedef basic_pipe< wchar_t > wpipe; typedef basic_pipebuf< char > pipebuf; typedef basic_pipebuf< wchar_t > wpipebuf; typedef basic_ipstream< char > ipstream; typedef basic_ipstream< wchar_t > wipstream; typedef basic_opstream< char > opstream; typedef basic_opstream< wchar_t > wopstream; typedef basic_pstream< char > pstream; typedef basic_pstream< wchar_t > wpstream; } } }
namespace boost { namespace process { namespace v1 { boost::process::v1::filesystem::path search_path(const boost::process::v1::filesystem::path &, const std::vector< boost::process::v1::filesystem::path > = ::boost::this_process::path()); } } }
namespace boost { namespace process { namespace v1 { template<typename ... Args> void spawn(Args &&...); } } }
namespace boost { namespace process { namespace v1 { template<typename ... Args> int system(Args &&...); } } }