Boost C++ 库

...世界上评价最高、设计最精良的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ Coding Standards

boost.process - Boost C++ 函数库

快速入门

启动一个进程需要四样东西

  • 一个 Asio execution_context / executor

  • 一个可执行文件的路径

  • 一个参数列表

  • 一系列可变参数的初始化器

example/quickstart.cpp:13-17
// process(asio::any_io_executor, filesystem::path, range<string> args, AdditionalInitializers...)
process proc(ctx.get_executor(), (1)
             "/usr/bin/cp", (2)
             {"source.txt", "target.txt"} (3)
); (4)
1 进程句柄的 executor
2 可执行文件的路径
3 参数列表,形式为 std::initializer_list
4 不是额外的初始化器

然后可以等待进程完成或终止进程。

生命周期

如果进程句柄离开作用域,它将终止子进程。您可以通过调用 proc.detach() 来阻止这种情况;但请注意,这可能会导致僵尸进程。

已完成的进程将返回一个退出码,可以通过对已退出的进程调用 .exit_code 来获取,它也是 .wait() 的返回值。

example/quickstart.cpp:22-23
process proc(ctx, "/bin/ls", {});
assert(proc.wait() == 0);

正常退出码是子进程从 main 返回的值;但是,posix 会添加有关进程的额外信息。这被称为 native_exit_code

可以使用 .running() 函数来检测进程是否仍在活动。

向子进程发送信号

父进程可以向子进程发送信号,要求执行某些操作。

.terminate 将导致子进程立即退出(在 posix 上为 SIGKILL)。这是终止子进程唯一可靠且可移植的方式。

example/quickstart.cpp:28-29
process proc(ctx, "/bin/totally-not-a-virus", {});
proc.terminate();

.request_exit 将请求子进程关闭(在 posix 上为 SIGTERM),子进程可能会忽略此请求。

example/quickstart.cpp:34-36
    process proc(ctx, "/bin/bash", {});
    proc.request_exit();
    proc.wait();

.interrupt 将向子进程发送 SIGINT 信号,子进程可能会将其解释为关机信号。

在 Windows 上,interrupt 需要设置 `windows::create_new_process_group` 初始化器
example/quickstart.cpp:41-43
    process proc(ctx, "/usr/bin/addr2line", {});
    proc.interrupt();
    proc.wait();

进程 v2 提供了 executeasync_execute 函数,可用于托管执行。

example/quickstart.cpp:48
    assert(execute(process(ctx, "/bin/ls", {})) == 0);

异步版本支持取消,并将按如下方式转发取消类型

  • asio::cancellation_type::total → interrupt

  • asio::cancellation_type::partial → request_exit

  • asio::cancellation_type::terminal → terminate

example/quickstart.cpp:53-56
    async_execute(process(ctx, "/usr/bin/g++", {"hello_world.cpp"}))
        (asio::cancel_after(std::chrono::seconds(10), asio::cancellation_type::partial)) (1)
        (asio::cancel_after(std::chrono::seconds(10), asio::cancellation_type::terminal)) (2)
        (asio::detached);
1 10 秒后发送 request_exit。
2 20 秒后终止

启动器

进程创建由 process_launcher 完成。process 的构造函数将使用默认的 launcher,该 launcher 因系统而异。大多数系统上还有其他可用的 launcher。

名称 总结 默认启动器 可用于

windows::default_launcher

CreateProcessW

windows

windows

windows::as_user_launcher

CreateProcessAsUserW

windows

windows::with_logon_launcher

CreateProcessWithLogonW

windows

windows::with_token_launcher

CreateProcessWithTokenW

windows

posix::default_launcher

fork & an error pipe

大多数 posix 系统

posix

posix::fork_and_forget

fork without error pipe

posix

posix::vfork_launcher

vfork

posix

通过调用运算符来调用启动器。

auto l = windows::as_user_launcher((HANDLE)0xDEADBEEF);
asio::io_context ctx;
boost::system::error_code ec;
auto proc = l(ctx, ec, "C:\\User\\boost\\Downloads\\totally_not_a_virus.exe", {});

启动器将调用初始化器上的某些函数(如果存在),如下文所述。初始化器用于修改进程行为。

Linux 启动器

Linux 上的默认启动器会打开一个内部管道,以将 fork 之后发生的错误通信回父进程。

管道可以被使用,如果一端在父进程中打开,另一端在子进程中。这允许父进程在其管道端进行选择,以得知子进程是否已退出。

可以使用 fork_and_forget_launcher 来防止这种情况。或者,vfork_launcher 可以直接将错误报告给父进程。

因此,对初始化器的某些调用发生在子进程 fork 之后。

struct custom_initializer
{
    // called before a call to fork. A returned error will cancel the launch.
    template<typename Launcher>
    error_code on_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));

    // called for every initializer if an error occurred during setup or process creation
    template<typename Launcher>
    void on_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line),
                  const error_code & ec);

    // called after successful process creation
    template<typename Launcher>
    void on_success(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));

    // called for every initializer if an error occurred when forking, in addition to on_error.
    template<typename Launcher>
    void on_fork_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line),
                       const error_code & ec);


    // called before a call to execve. A returned error will cancel the launch. Called from the child process.
    template<typename Launcher>
    error_code on_exec_setup(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));


    // called after a failed call to execve from the child process.
    template<typename Launcher>
    void on_exec_error(Launcher & launcher, const filesystem::path &executable, const char * const * (&cmd_line));
};

成功时的调用序列

posix success

fork 失败时的调用序列

posix fork err

exec 失败时的调用序列

posix exec err

启动器将在 on_exec_setup 之后关闭所有非白名单文件描述符。

Windows 启动器

Windows 启动器非常直接,如果初始化器存在,它们将调用初始化器上的以下函数。

struct custom_initializer
{
    // called before a call to CreateProcess. A returned error will cancel the launch.
    template<typename Launcher>
    error_code on_setup(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line);

    // called for every initializer if an error occurred during setup or process creation
    template<typename Launcher>
    void on_error(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line,
                  const error_code & ec);

    // called after successful process creation
    template<typename Launcher>
    void on_success(Launcher & launcher, const filesystem::path &executable, std::wstring &cmd_line);

};

注意:所有额外的 Windows 启动器都继承 default_launcher

调用序列如下

image::windows_exec.svg '''

初始化器

process_start_dir

最容易使用的初始化器是 process_start_dir

example/start_dir.cpp:17-20
asio::io_context ctx;
process ls(ctx.get_executor(), "/ls", {}, process_start_dir("/home"));
ls.wait();

这将会在 /home 文件夹中运行 ls,而不是当前文件夹。

如果您的路径是相对路径,它可能会在 posix 上失败,因为在调用 execve 之前目录已更改。 = stdio

当使用 io 与子进程交互时,所有三个标准流(stdin、stdout、stderr)都会为子进程设置。默认设置是继承父进程。

此功能旨在灵活,因此对分配给其中一个流的参数几乎没有检查。

管道

可以使用 Asio 管道进行 io。当在 process_stdio 中使用时,它们将自动连接,另一端将分配给子进程。

example/stdio.cpp:20-29
asio::io_context ctx;
asio::readable_pipe rp{ctx};

process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, rp, { /* err to default */ }});
std::string output;

boost::system::error_code ec;
asio::read(rp, asio::dynamic_buffer(output), ec);
assert(!ec || (ec == asio::error::eof));
proc.wait();

可读管道可以分配给 outerr,而可写管道可以分配给 in

FILE*

FILE* 也可以用于任何一端;这允许使用 stdin、stderr、stdout 宏。

example/stdio.cpp:35-38
    asio::io_context ctx;
    // forward both stderr & stdout to stdout of the parent process
    process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, stdout, stdout});
    proc.wait();

nullptr

nullptr 可用于将给定流设置为在 null 设备上打开(在 posix 上为 /dev/null,在 windows 上为 NUL)。这用于忽略输出或仅提供 EOF 作为输入。

example/stdio.cpp:43-46
    asio::io_context ctx;
    // forward stderr to /dev/null or NUL
    process proc(ctx, "/usr/bin/g++", {"--version"}, process_stdio{{ /* in to default */}, {}, nullptr});
    proc.wait();

native_handle

也可以使用原生句柄,这意味着在 posix 上是 int,在 windows 上是 HANDLE。此外,任何具有返回有效 stdio 流类型的值的 native_handle 函数的对象。

例如 Linux 上的域套接字。

example/stdio.cpp:52-57
    asio::io_context ctx;
    // ignore stderr
    asio::local::stream_protocol::socket sock{ctx}, other{ctx};
    asio::local::connect_pair(sock, other);
    process proc(ctx, "~/not-a-virus", {}, process_stdio{sock, sock, nullptr});
    proc.wait();

popen

此外,process v2 提供了一个 popen 类。它启动一个进程并连接 stdin 和 stdout 的管道,以便 popen 对象可以用作流。

example/stdio.cpp:63-66
    asio::io_context ctx;
    boost::process::popen proc(ctx, "/usr/bin/addr2line", {argv[0]});
    asio::write(proc, asio::buffer("main\n"));
    std::string line;
    asio::read_until(proc, asio::dynamic_buffer(line), '\n');

环境变量

environment 命名空间提供了各种工具来查询和操作当前进程的环境。

API 应该很简单,但有一个奇怪之处需要指出,即在 Windows 上环境名称不区分大小写。key_traits 类根据当前系统实现适当的特性。

此外,环境可以是分隔为 :; 的列表;environment::valueenvironment::value_view 可用于迭代这些列表。

除此之外,对环境的要求尽可能低;环境要么是字符串列表,要么是字符串对列表。然而,建议使用环境类型,以便进行正确的相等性比较。

需要注意的是 find_executable 函数,它在环境中搜索可执行文件。

example/env.cpp:19-28
    // search in the current environment
    auto exe = environment::find_executable("g++");

    std::unordered_map <environment::key, environment::value> my_env =
        {
            {"SECRET", "THIS_IS_A_TEST"},
            {"PATH",   {"/bin", "/usr/bin"}}
        };

    auto other_exe = environment::find_executable("g++", my_env);

子进程环境

子进程环境的赋值遵循相同的约束

example/env.cpp:34-42
    asio::io_context ctx;
    std::unordered_map<environment::key, environment::value> my_env =
        {
            {"SECRET", "THIS_IS_A_TEST"},
            {"PATH", {"/bin", "/usr/bin"}}
        };
    auto exe = environment::find_executable("g++", my_env);
    process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));
    process pro2(ctx, exe, {"test.cpp"}, process_environment(my_env));

继承环境

可以通过调用 environment::current 来获取当前环境,它返回 environment::key_value_pair_view 的正向范围。

example/env.cpp:48-54
    asio::io_context ctx;
    auto c = environment::current();
    // we need to use a value, since windows needs wchar_t.
    std::vector<environment::key_value_pair> my_env{c.begin(), c.end()};
    my_env.push_back("SECRET=THIS_IS_A_TEST");
    auto exe = environment::find_executable("g++", my_env);
    process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));

或者,您可以使用 map 容器来表示环境。

example/env.cpp:61-68
    asio::io_context ctx;
    std::unordered_map<environment::key, environment::value> my_env;
    for (const auto & kv : environment::current())
      if (kv.key().string() != "SECRET")
        my_env[kv.key()] = kv.value();

    auto exe = environment::find_executable("g++", my_env);
    process proc(ctx, exe, {"main.cpp"}, process_environment(my_env));

参考

bind_launcher.hpp

bind_launcher 工具允许在运行时动态构建具有绑定初始化器的启动器。

// Creates a new launcher with the bound initializer.
template<typename Launcher, typename ... Init>
auto bind_launcher(Launcher && launcher, Init && ... init);

// Calls bind_launcher with the default_launcher as the first parameter.
// The new launcher with bound parameters
template<typename ... Init>
auto bind_default_launcher(Init && ... init);

cstring_ref.hpp

cstring_ref 是一个类似 string-view 的类型,它保存一个以 null 结尾的字符串。== default_launcher.hpp

default_launcher 是创建进程的标准方法。

asio::io_context ctx;
process proc(ctx.get_executor(), "test", {});
// equivalent to
process prod = default_launcher()(ctx.get_executor(), "test", {});

它有四种重载

(ExecutionContext &,              filesystem::path, Args && args, Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>
(Executor &,                      filesystem::path, Args && args, Inits && ... inits) -> basic_process<Executor>;
(ExecutionContext &, error_code&, filesystem::path, Args && args, Inits && ... inits) -> basic_process<typename ExecutionContext::executor_type>;`
(Executor &,         error_code&, filesystem::path, Args && args, Inits && ... inits) -> basic_process<Executor>

environment.hpp

环境

environment 头文件提供了操作当前环境并为其设置新进程的工具。

环境是一个 range of T,满足以下要求:

对于 T value* - std::get<0>(value) 必须返回一个可与 key_view 比较的类型。* - std::get<1>(value) 必须返回一个可转换为 value_view 的类型。

// Namespace for information and functions regarding the calling process.
namespace environment
{

// A char traits type that reflects the OS rules for string representing environment keys.
/* Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`.
 *
 * Windows treats keys as case-insensitive yet preserving. The char traits are made to reflect
 * that behaviour.
*/
template<typename Char>
using key_char_traits = implementation_defined ;

// A char traits type that reflects the OS rules for string representing environment values.
/* Can be an alias of std::char_traits. May only be defined for `char` and `wchar_t`.
*/
template<typename Char>
using value_char_traits = implementation_defined ;

// The character type used by the environment. Either `char` or `wchar_t`.
using char_type = implementation_defined ;

// The equal character in an environment string used to separate key and value.
constexpr char_type equality_sign = implementation_defined;

// The delimiter in environemtn lists. Commonly used by the `PATH` variable.
constexpr char_type delimiter = implementation_defined;

// The native handle of an environment. Note that this can be an owning pointer and is generally not thread safe.
using native_handle = implementation_defined;


// A forward iterator over string_view used by a value or value_view to iterator through environments that are lists.
struct value_iterator;

// A view type for a key of an environment
struct key_view
{
    using value_type       = char_type;
    using traits_type      = key_char_traits<char_type>;
    using string_view_type = basic_string_view<char_type, traits_type>;
    using string_type      = std::basic_string<char_type, key_char_traits<char_type>>;

    key_view() noexcept = default;
    key_view( const key_view& p ) = default;
    key_view( key_view&& p ) noexcept = default;
    template<typename Source>
    key_view( const Source& source );
    key_view( const char_type * p);
    key_view(       char_type * p);

    ~key_view() = default;

    key_view& operator=( const key_view& p ) = default;
    key_view& operator=( key_view&& p ) noexcept = default;
    key_view& operator=( string_view_type source );

    void swap( key_view& other ) noexcept;

    string_view_type native() const noexcept;

    operator string_view_type() const;

    int compare( const key_view& p ) const noexcept;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;

    std::string       string() const;
    std::wstring     wstring() const;

    string_type native_string() const;

    friend bool operator==(key_view l, key_view r);
    friend bool operator!=(key_view l, key_view r);
    friend bool operator<=(key_view l, key_view r);
    friend bool operator>=(key_view l, key_view r);
    friend bool operator< (key_view l, key_view r);
    friend bool operator> (key_view l, key_view r);

    bool empty() const;

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key_view& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key_view& p );

    const value_type * data() const;
    std::size_t size() const;
};

// A view for a value in an environment
struct value_view
{
    using value_type       = char_type;
    using string_view_type = basic_cstring_ref<char_type, value_char_traits<char_type>>;
    using string_type      = std::basic_string<char_type, value_char_traits<char_type>>;
    using traits_type      = value_char_traits<char_type>;

    value_view() noexcept = default;
    value_view( const value_view& p ) = default;
    value_view( value_view&& p ) noexcept = default;
    template<typename Source>
    value_view( const Source& source );
    value_view( const char_type * p);
    value_view(       char_type * p);

    ~value_view() = default;

    value_view& operator=( const value_view& p ) = default;
    value_view& operator=( value_view&& p ) noexcept = default;
    value_view& operator=( string_view_type source );

    void swap( value_view& other ) noexcept;

    string_view_type native() const noexcept ;

    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;

    int compare( const value_view& p ) const noexcept;
    int compare( string_view_type str ) const;
    int compare( const value_type* s )  const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc() ) const;
    std::string string() const;
    std::wstring wstring() const;

    string_type native_string() const;
    bool empty() const;

    friend bool operator==(value_view l, value_view r);
    friend bool operator!=(value_view l, value_view r);
    friend bool operator<=(value_view l, value_view r);
    friend bool operator>=(value_view l, value_view r);
    friend bool operator< (value_view l, value_view r);
    friend bool operator> (value_view l, value_view r);


    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const value_view& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, value_view& p );
    value_iterator begin() const;
    value_iterator   end() const;

    const char_type * c_str();
    const value_type * data() const;
    std::size_t size() const;
};

// A view for a key value pair in an environment
struct key_value_pair_view
{
  using value_type       = char_type;
  using string_type      = std::basic_string<char_type>;
  using string_view_type = basic_cstring_ref<char_type>;
  using traits_type      = std::char_traits<char_type>;

  key_value_pair_view() noexcept = default;
  key_value_pair_view( const key_value_pair_view& p ) = default;
  key_value_pair_view( key_value_pair_view&& p ) noexcept = default;
  template<typename Source,
           typename = typename std::enable_if<is_constructible<string_view_type, Source>::value>::type>
  key_value_pair_view( const Source& source );

  key_value_pair_view( const char_type * p);
  key_value_pair_view(       char_type * p);


  ~key_value_pair_view() = default;

  key_value_pair_view& operator=( const key_value_pair_view& p ) = default;
  key_value_pair_view& operator=( key_value_pair_view&& p ) noexcept = default;

  void swap( key_value_pair_view& other ) noexcept;

  string_view_type native() const noexcept;

  operator string_view_type() const;
  operator typename string_view_type::string_view_type() const;

  int compare( key_value_pair_view p ) const noexcept;
  int compare( const string_type& str ) const;
  int compare( string_view_type str ) const;
  int compare( const value_type* s ) const;

  template< class CharT, class Traits = std::char_traits<CharT>, class Alloc = std::allocator<CharT> >
  std::basic_string<CharT,Traits,Alloc>
  basic_string( const Alloc& alloc = Alloc()) const;
  std::string   string() const;
  std::wstring wstring() const;

  string_type native_string() const;

  bool empty() const;

  key_view key() const;
  value_view value() const;

  friend bool operator==(key_value_pair_view l, key_value_pair_view r);
  friend bool operator!=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator<=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator>=(key_value_pair_view l, key_value_pair_view r);
  friend bool operator< (key_value_pair_view l, key_value_pair_view r);
  friend bool operator> (key_value_pair_view l, key_value_pair_view r);


  template< class CharT, class Traits >
  friend std::basic_ostream<CharT,Traits>&
  operator<<( std::basic_ostream<CharT,Traits>& os, const key_value_pair_view& p );

  template< class CharT, class Traits >
  friend std::basic_istream<CharT,Traits>&
  operator>>( std::basic_istream<CharT,Traits>& is, key_value_pair_view& p );

  template<std::size_t Idx>
  inline auto get() const -> typename conditional<Idx == 0u, key_view,
                                                             value_view>::type;
  const value_type * c_str() const noexcept;
  const value_type * data() const;
  std::size_t size() const;

};

// Allow tuple-likg getters & structured bindings.
template<>   key_view key_value_pair_view::get<0u>() const;
template<> value_view key_value_pair_view::get<1u>() const;

// A class representing a key within an environment.
struct key
{
    using value_type       = char_type;
    using traits_type      = key_char_traits<char_type>;
    using string_type      = std::basic_string<char_type, traits_type>;
    using string_view_type = basic_string_view<char_type, traits_type>;

    key();
    key( const key& p ) = default;
    key( key&& p ) noexcept = default;
    key( const string_type& source );
    key( string_type&& source );
    key( const value_type * raw );
    key(       value_type * raw );

    explicit key(key_view kv);


    template< class Source >
    key( const Source& source);

    key(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt >
    key( InputIt first, InputIt last);

    ~key() = default;

    key& operator=( const key& p ) = default;
    key& operator=( key&& p );
    key& operator=( string_type&& source );

    template< class Source >
    key& operator=( const Source& source );

    key& assign( string_type&& source );

    template< class Source >
    key& assign( const Source& source );
    template< class InputIt >
    key& assign( InputIt first, InputIt last );

    void clear();

    void swap( key& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;

    int compare( const key& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
        class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;


    std::string string() const;
    std::wstring wstring() const;

    const string_type & native_string() const;
    bool empty() const;

    friend bool operator==(const key & l, const key & r);
    friend bool operator!=(const key & l, const key & r);
    friend bool operator<=(const key & l, const key & r);
    friend bool operator>=(const key & l, const key & r);
    friend bool operator< (const key & l, const key & r);
    friend bool operator> (const key & l, const key & r);

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key& p );
    const value_type * data() const;
    std::size_t size() const;
};

bool operator==(const value_view &, const value_view);
bool operator!=(const value_view &, const value_view);
bool operator<=(const value_view &, const value_view);
bool operator< (const value_view &, const value_view);
bool operator> (const value_view &, const value_view);
bool operator>=(const value_view &, const value_view);


struct value
{
    using value_type       = char_type;
    using traits_type      = value_char_traits<char_type>;
    using string_type      = std::basic_string<char_type, traits_type>;
    using string_view_type = basic_cstring_ref<char_type, traits_type>;

    value();
    value( const value& p ) = default;

    value( const string_type& source );
    value( string_type&& source );
    value( const value_type * raw );
    value(       value_type * raw );

    explicit value(value_view kv);

    template< class Source >
    value( const Source& source );
    value(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt >
    value( InputIt first, InputIt last);

    ~value() = default;

    value& operator=( const value& p ) = default;
    value& operator=( value&& p );
    value& operator=( string_type&& source );
    template< class Source >
    value& operator=( const Source& source );

    value& assign( string_type&& source );
    template< class Source >
    value& assign( const Source& source );

    template< class InputIt >
    value& assign( InputIt first, InputIt last );

    void push_back(const value & sv);
    void clear() {value_.clear();}

    void swap( value& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;

    int compare( const value& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>,
            class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc()) const;

    std::string string() const;
    std::wstring wstring() const;


    const string_type & native_string() const;

    bool empty() const;

    friend bool operator==(const value & l, const value & r);
    friend bool operator!=(const value & l, const value & r);
    friend bool operator<=(const value & l, const value & r);
    friend bool operator>=(const value & l, const value & r);
    friend bool operator< (const value & l, const value & r);
    friend bool operator> (const value & l, const value & r);

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const value& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, value& p );

    value_iterator begin() const;
    value_iterator   end() const;
    const value_type * data() const;
    std::size_t size() const;
};


bool operator==(const value_view &, const value_view);
bool operator!=(const value_view &, const value_view);
bool operator<=(const value_view &, const value_view);
bool operator< (const value_view &, const value_view);
bool operator> (const value_view &, const value_view);
bool operator>=(const value_view &, const value_view);

struct key_value_pair
{
    using value_type       = char_type;
    using traits_type      = std::char_traits<char_type>;
    using string_type      = std::basic_string<char_type>;
    using string_view_type = basic_cstring_ref<char_type>;

    key_value_pair()l
    key_value_pair( const key_value_pair& p ) = default;
    key_value_pair( key_value_pair&& p ) noexcept = default;
    key_value_pair(key_view key, value_view value);

    key_value_pair(key_view key, std::initializer_list<basic_string_view<char_type>> values);
    key_value_pair( const string_type& source );
    key_value_pair( string_type&& source );
    key_value_pair( const value_type * raw );
    key_value_pair(       value_type * raw );

    explicit key_value_pair(key_value_pair_view kv) : value_(kv.c_str()) {}

    template< class Source >
    key_value_pair( const Source& source);

    template< typename Key,
              typename Value >
    key_value_pair(
         const std::pair<Key, Value> & kv);

    key_value_pair(const typename conditional<is_same<value_type, char>::value, wchar_t, char>::type  * raw);

    template< class InputIt , typename std::iterator_traits<InputIt>::iterator_category>
    key_value_pair( InputIt first, InputIt last );

    ~key_value_pair() = default;

    key_value_pair& operator=( const key_value_pair& p ) = default;
    key_value_pair& operator=( key_value_pair&& p );
    key_value_pair& operator=( string_type&& source );
    template< class Source >
    key_value_pair& operator=( const Source& source );

    key_value_pair& assign( string_type&& source );

    template< class Source >
    key_value_pair& assign( const Source& source );


    template< class InputIt >
    key_value_pair& assign( InputIt first, InputIt last );

    void clear();

    void swap( key_value_pair& other ) noexcept;

    const value_type* c_str() const noexcept;
    const string_type& native() const noexcept;
    string_view_type native_view() const noexcept;

    operator string_type() const;
    operator string_view_type() const;
    operator typename string_view_type::string_view_type() const;
    operator key_value_pair_view() const;

    int compare( const key_value_pair& p ) const noexcept;
    int compare( const string_type& str ) const;
    int compare( string_view_type str ) const;
    int compare( const value_type* s ) const;

    template< class CharT, class Traits = std::char_traits<CharT>, class Alloc = std::allocator<CharT> >
    std::basic_string<CharT,Traits,Alloc>
    basic_string( const Alloc& alloc = Alloc() ) const;
    std::string string() const       {return basic_string<char>();}
    std::wstring wstring() const     {return basic_string<wchar_t>();}

    const string_type & native_string() const;
    friend bool operator==(const key_value_pair & l, const key_value_pair & r);
    friend bool operator!=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator<=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator>=(const key_value_pair & l, const key_value_pair & r);
    friend bool operator< (const key_value_pair & l, const key_value_pair & r);
    friend bool operator> (const key_value_pair & l, const key_value_pair & r);

    bool empty() const;

    struct key_view key() const;
    struct value_view value() const;

    template< class CharT, class Traits >
    friend std::basic_ostream<CharT,Traits>&
    operator<<( std::basic_ostream<CharT,Traits>& os, const key_value_pair& p );

    template< class CharT, class Traits >
    friend std::basic_istream<CharT,Traits>&
    operator>>( std::basic_istream<CharT,Traits>& is, key_value_pair& p );

    const value_type * data() const;
    std::size_t size() const;

    template<std::size_t Idx>
    inline auto get() const -> typename conditional<Idx == 0u,environment::key_view, environment::value_view>::type;
};

bool operator==(const key_value_pair_view &, const key_value_pair_view);
bool operator!=(const key_value_pair_view &, const key_value_pair_view);
bool operator<=(const key_value_pair_view &, const key_value_pair_view);
bool operator< (const key_value_pair_view &, const key_value_pair_view);
bool operator> (const key_value_pair_view &, const key_value_pair_view);
bool operator>=(const key_value_pair_view &, const key_value_pair_view);


// Allow tuple-likg getters & structured bindings.
template<>
key_view key_value_pair::get<0u>() const;

template<>
value_view key_value_pair::get<1u>() const;

// A view object for the current environment of this process.
/*
 * The view might (windows) or might not (posix) be owning;
 * if it owns it will deallocate the on destruction, like a unique_ptr.
 *
 * Note that accessing the environment in this way is not thread-safe.
 *
 * @code
 *
 * void dump_my_env(current_view env = current())
 * {
 *    for (auto  & [k, v] : env)
 *        std::cout << k.string() << " = "  << v.string() << std::endl;
 * }
 *
 * @endcode
 *
 *
 */
struct current_view
{
    using native_handle_type = environment::native_handle_type;
    using value_type = key_value_pair_view;

    current_view() = default;
    current_view(current_view && nt) = default;

    native_handle_type  native_handle() { return handle_.get(); }

    struct iterator
    {
        using value_type        = key_value_pair_view;
        using difference_type   = int;
        using reference         = key_value_pair_view;
        using pointer           = key_value_pair_view;
        using iterator_category = std::forward_iterator_tag;

        iterator() = default;
        iterator(const iterator & ) = default;
        iterator(const native_iterator &native_handle) : iterator_(native_handle) {}

        iterator & operator++()
        {
            iterator_ = detail::next(iterator_);
            return *this;
        }

        iterator operator++(int)
        {
            auto last = *this;
            iterator_ = detail::next(iterator_);
            return last;
        }
        key_value_pair_view operator*() const
        {
            return detail::dereference(iterator_);
        }

        friend bool operator==(const iterator & l, const iterator & r) {return l.iterator_ == r.iterator_;}
        friend bool operator!=(const iterator & l, const iterator & r) {return l.iterator_ != r.iterator_;}

      private:
        environment::native_iterator iterator_;
    };

    iterator begin() const {return iterator(handle_.get());}
    iterator   end() const {return iterator(detail::find_end(handle_.get()));}

 private:

  std::unique_ptr<typename remove_pointer<native_handle_type>::type,
                    detail::native_handle_deleter> handle_{environment::detail::load_native_handle()};
};

// Obtain a handle to the current environment
inline current_view current() {return current_view();}

// Find the home folder in an environment-like type.
/*
 * @param env The environment to search. Defaults to the current environment of this process
 *
 * The environment type passed in must be a range with value T that fulfills the following requirements:
 *
 *  For `T value`
 *
 *  - std::get<0>(value) must return a type comparable to `key_view`.
 *  - std::get<1>(value) must return a type convertible to filesystem::path.
 *
 * @return A filesystem::path to the home directory or an empty path if it cannot be found.
 *
 */
template<typename Environment = current_view>
inline filesystem::path home(Environment && env = current());

// Find the executable `name` in an environment-like type.
template<typename Environment = current_view>
filesystem::path find_executable(BOOST_PROCESS_V2_NAMESPACE::filesystem::path name,
                                 Environment && env = current());

// Get an environment variable from the current process.
value get(const key & k, error_code & ec);
value get(const key & k);
value get(basic_cstring_ref<char_type, key_char_traits<char_type>> k, error_code & ec);
value get(basic_cstring_ref<char_type, key_char_traits<char_type>> k);
value get(const char_type * c, error_code & ec);
value get(const char_type * c);

// Set an environment variable for the current process.
void set(const key & k, value_view vw, error_code & ec);
void set(const key & k, value_view vw);
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, value_view vw, error_code & ec);
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, value_view vw);
void set(const char_type * k, value_view vw, error_code & ec);
void set(const char_type * k, value_view vw);
template<typename Char>
void set(const key & k, const Char * vw, error_code & ec);
template<typename Char>
void set(const key & k, const Char * vw);
template<typename Char>
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, const Char * vw, error_code & ec);
template<typename Char>
void set(basic_cstring_ref<char_type, key_char_traits<char_type>> k, const Char * vw);
template<typename Char>
void set(const char_type * k, const Char * vw, error_code & ec);
template<typename Char>
void set(const char_type * k, const Char * vw);

//  Remove an environment variable from the current process.
void unset(const key & k, error_code & ec);
void unset(const key & k);
void unset(basic_cstring_ref<char_type, key_char_traits<char_type>> k, error_code & ec);
void unset(basic_cstring_ref<char_type, key_char_traits<char_type>> k);
void unset(const char_type * c, error_code & ec);
void unset(const char_type * c);

}

process_environment

为了设置子进程的环境,可以使用 process_environment

这将为子进程设置环境
process proc{executor, find_executable("printenv"), {"foo"}, process_environment{"foo=bar"}};

环境初始化器将持久化其状态,以便可以多次使用。但请注意,操作系统允许修改其内部状态。

auto exe = find_executable("printenv");
process_environment env = {"FOO=BAR", "BAR=FOO"};

process proc1(executor, exe, {"FOO"}, env);
process proc2(executor, exe, {"BAR"}, env);

error.hpp

error 头文件提供了两个错误类别

// Errors used for utf8 <-> UCS-2 conversions.
enum utf8_conv_error
{
    insufficient_buffer = 1,
    invalid_character,
};

extern const error_category& get_utf8_category();
static const error_category& utf8_category = get_utf8_category();

extern const error_category& get_exit_code_category();

/// An error category that can be used to interpret exit codes of subprocesses.
static const error_category& exit_code_category = get_exit_code_category();

}

get_exit_code_category 可以按如下方式使用

void run_my_process(filesystem::path pt, error_code & ec)
{
  process proc(pt, {});
  proc.wait();
  ec.assign(proc.native_exit_code(), error::get_exit_code_category());
}

execute.hpp

execute 头文件提供了两个错误类别

// Run a process and wait for it to complete.
template<typename Executor> int execute(basic_process<Executor> proc);
template<typename Executor> int execute(basic_process<Executor> proc, error_code & ec)

// Execute a process asynchronously
template<typename Executor = net::any_io_executor,
        BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
        WaitHandler = net::default_completion_token_t<Executor>>
auto async_execute(basic_process<Executor> proc,
                         WaitHandler && handler = net::default_completion_token_t<Executor>());

async_execute 函数异步等待进程完成。

Cancelling the execution will signal the child process to exit
with the following interpretations:
  • cancellation_type::total → interrupt

  • cancellation_type::partial → request_exit

  • cancellation_type::terminal → terminate

需要注意的是,async_execute 将使用最低的选定取消类型。子进程可能会忽略除 terminal 之外的任何内容。

exit_code.hpp

exit code 头文件提供了可移植的退出码处理程序。

// The native exit-code type, usually an integral value
/* The OS may have a value different from `int` to represent
 * the exit codes of subprocesses. It might also
 * contain additional information.
 */
typedef implementation_defined native_exit_code_type;


// Check if the native exit code indicates the process is still running
bool process_is_running(native_exit_code_type code);

// Obtain the portable part of the exit code, i.e. what the subprocess has returned from main.
int evaluate_exit_code(native_exit_code_type code);

// Helper to subsume an exit-code into an error_code if there's no actual error isn't set.
error_code check_exit_code(
    error_code &ec, native_exit_code_type native_code,
    const error_category & category = error::get_exit_code_category());

check_exit_code 可以像这样使用

process proc{co_await this_coro::executor, "exit", {"1"}};

co_await proc.async_wait(
    asio::deferred(
     [&proc](error_code ec, int)
     {
       return asio::deferred.values(
                 check_exit_code(ec, proc.native_exit_code())
             );

ext

process/ext 中的头文件提供了有关第三方进程的信息。

// Get the cmd line used to launche the process
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle);
shell cmd(pid_type pid, error_code & ec);
shell cmd(pid_type pid);


// Get the current working directory of the process.
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle)
filesystem::path cwd(pid_type pid, error_code & ec);
filesystem::path cwd(pid_type pid);


// Get the current environment of the process.
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle)
env_view env(pid_type pid, error_code & ec);
env_view env(pid_type pid);


// Get the executable of the process.
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle)
filesystem::path exe(pid_type pid, error_code & ec);
filesystem::path exe(pid_type pid);
在某些小众平台上,该函数可能会因“operation_not_supported”而失败。
在 Windows 上,还提供了接受 HANDLE 的重载。

pid.hpp

//An integral type representing a process id.
typedef implementation_defined pid_type;

// Get the process id of the current process.
pid_type current_pid();

// List all available pids.
std::vector<pid_type> all_pids(boost::system::error_code & ec);
std::vector<pid_type> all_pids();

// return parent pid of pid.
pid_type parent_pid(pid_type pid, boost::system::error_code & ec);
pid_type parent_pid(pid_type pid);

// return child pids of pid.
std::vector<pid_type> child_pids(pid_type pid, boost::system::error_code & ec);
std::vector<pid_type> child_pids(pid_type pid);

popen.hpp

popen 是一个类,它启动一个进程并将 stdin 和 stderr 连接到管道。

popen proc(executor, find_executable("addr2line"), {argv[0]});
asio::write(proc, asio::buffer("main\n"));
std::string line;
asio::read_until(proc, asio::dynamic_buffer(line), '\n');
// A subprocess with automatically assigned pipes.
template<typename Executor = net::any_io_executor>
struct basic_popen : basic_process<Executor>
{
    // The executor of the process
    using executor_type = Executor;

    // Rebinds the popen type to another executor.
    template <typename Executor1>
    struct rebind_executor
    {
        // The pipe type when rebound to the specified executor.
        typedef basic_popen<Executor1> other;
    };

    // Move construct a popen
    basic_popen(basic_popen &&) = default;
    // Move assign a popen
    basic_popen& operator=(basic_popen &&) = default;

    // Move construct a popen and change the executor type.
    template<typename Executor1>
    basic_popen(basic_popen<Executor1>&& lhs)
        : basic_process<Executor>(std::move(lhs)),
                stdin_(std::move(lhs.stdin_)), stdout_(std::move(lhs.stdout_))
    {
    }

    // Create a closed process handle
    explicit basic_popen(executor_type exec);

    // Create a closed process handle
    template <typename ExecutionContext>
    explicit basic_popen(ExecutionContext & context);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ... Inits>
    explicit basic_popen(
            executor_type executor,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            executor_type executor,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Args, typename ... Inits>
    explicit basic_popen(
            executor_type executor,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename Args, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            executor_type executor,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ExecutionContext, typename ... Inits>
    explicit basic_popen(
            ExecutionContext & context,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

        // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ExecutionContext, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            ExecutionContext & context,
            const filesystem::path& exe,
            std::initializer_list<string_view> args,
            Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename ExecutionContext, typename Args, typename ... Inits>
    explicit basic_popen(
            ExecutionContext & context,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // Construct a child from a property list and launch it using the default process launcher.
    template<typename Launcher, typename ExecutionContext, typename Args, typename ... Inits>
    explicit basic_popen(
            Launcher && launcher,
            ExecutionContext & context,
            const filesystem::path& exe,
            Args&& args, Inits&&... inits);

    // The type used for stdin on the parent process side.
    using stdin_type = net::basic_writable_pipe<Executor>;
    // The type used for stdout on the parent process side.
    using stdout_type = net::basic_readable_pipe<Executor>;

    // Get the stdin pipe.

    // Get the stdout pipe.

    // Get the stdin pipe.
          stdin_type & get_stdin();
    const stdin_type & get_stdin() const;
    // Get the stdout pipe.
          stdout_type & get_stdout();
    const stdout_type & get_stdout() const;

    // Write some data to the stdin pipe.
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers);
    template <typename ConstBufferSequence>
    std::size_t write_some(const ConstBufferSequence& buffers,
                           boost::system::error_code& ec);

    // Start an asynchronous write.
    template <typename ConstBufferSequence,
            BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code, std::size_t))
            WriteToken = net::default_completion_token_t<executor_type>>
    auto async_write_some(const ConstBufferSequence& buffers,
                     WriteToken && token = net::default_completion_token_t<executor_type>());

    // Read some data from the stdout pipe.
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers);
    template <typename MutableBufferSequence>
    std::size_t read_some(const MutableBufferSequence& buffers,
                          boost::system::error_code& ec)

    // Start an asynchronous read.    template <typename MutableBufferSequence,
            BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (boost::system::error_code, std::size_t))
            ReadToken = net::default_completion_token_t<executor_type>>
    auto async_read_some(const MutableBufferSequence& buffers,
                    BOOST_ASIO_MOVE_ARG(ReadToken) token
                    = net::default_completion_token_t<executor_type>());
};

// A popen object with the default  executor.
using popen = basic_popen<>;

process.hpp

// A class managing a subprocess
/* A `basic_process` object manages a subprocess; it tracks the status and exit-code,
 * and will terminate the process on destruction if `detach` was not called.
*/
template<typename Executor = net::any_io_executor>
struct basic_process
{
  // The executor of the process
  using executor_type = Executor;
  // Get the executor of the process
  executor_type get_executor() {return process_handle_.get_executor();}

  // The non-closing handle type
  using handle_type = basic_process_handle<executor_type>;

  // Get the underlying non-closing handle
  handle_type & handle() { return process_handle_; }

  // Get the underlying non-closing handle
  const handle_type & handle() const { return process_handle_; }

  // Provides access to underlying operating system facilities
  using native_handle_type = typename handle_type::native_handle_type;

  // Rebinds the process_handle to another executor.
  template <typename Executor1>
  struct rebind_executor
  {
    // The socket type when rebound to the specified executor.
    typedef basic_process<Executor1> other;
  };

  /** An empty process is similar to a default constructed thread. It holds an empty
  handle and is a place holder for a process that is to be launched later. */
  basic_process() = default;

  basic_process(const basic_process&) = delete;
  basic_process& operator=(const basic_process&) = delete;

  // Move construct the process. It will be detached from `lhs`.
  basic_process(basic_process&& lhs) = default;

  // Move assign a process. It will be detached from `lhs`.
  basic_process& operator=(basic_process&& lhs) = default;

  // Move construct and rebind the executor.
  template<typename Executor1>
  basic_process(basic_process<Executor1>&& lhs);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename ... Inits>
  explicit basic_process(
      executor_type executor,
      const filesystem::path& exe,
      std::initializer_list<string_view> args,
      Inits&&... inits);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename Args, typename ... Inits>
  explicit basic_process(
      executor_type executor,
      const filesystem::path& exe,
      Args&& args, Inits&&... inits);

  // Construct a child from a property list and launch it using the default launcher..
  template<typename ExecutionContext, typename ... Inits>
  explicit basic_process(
          ExecutionContext & context,
          const filesystem::path& exe,
          std::initializer_list<string_view> args,
          Inits&&... inits);
  // Construct a child from a property list and launch it using the default launcher.
  template<typename ExecutionContext, typename Args, typename ... Inits>
  explicit basic_process(
          ExecutionContext & context,
          const filesystem::path&>::type exe,
          Args&& args, Inits&&... inits);

  // Attach to an existing process
  explicit basic_process(executor_type exec, pid_type pid);

  // Attach to an existing process and the internal handle
  explicit basic_process(executor_type exec, pid_type pid, native_handle_type native_handle);

  // Create an invalid handle
  explicit basic_process(executor_type exec);

  // Attach to an existing process
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context, pid_type pid);

  // Attach to an existing process and the internal handle
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context, pid_type pid, native_handle_type native_handle);

  // Create an invalid handle
  template <typename ExecutionContext>
  explicit basic_process(ExecutionContext & context);



  // Destruct the handle and terminate the process if it wasn't detached.
  ~basic_process();

  // Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
  /** Maybe be ignored by the subprocess. */
  void interrupt(error_code & ec);
  void interrupt();

  // Throwing @overload void interrupt()


  // Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
  void request_exit(error_code & ec);
  void request_exit();

  // Send the process a signal requesting it to stop. This may rely on undocumented functions.
  void suspend(error_code &ec);
  void suspend();


  // Send the process a signal requesting it to resume. This may rely on undocumented functions.
  void resume(error_code &ec);
  void resume();

  // Unconditionally terminates the process and stores the exit code in exit_status.
  void terminate(error_code & ec);
  void terminate();

  // Waits for the process to exit, store the exit code internally and return it.
  int wait(error_code & ec);
  int wait();

  // Detach the process.
  handle_type detach();
  // Get the native
  native_handle_type native_handle() {return process_handle_.native_handle(); }

  // Return the evaluated exit_code.
  int exit_code() cons;

  // Get the id of the process;
  pid_type id() const;

  // The native handle of the process.
  /** This might be undefined on posix systems that only support signals */
  native_exit_code_type native_exit_code() const;

  // Checks if the current process is running.
  /* If it has already completed the exit code will be stored internally
   * and can be obtained by calling `exit_code.
   */
  bool running();
  bool running(error_code & ec) noexcept;

  // Check if the process is referring to an existing process.
  /** Note that this might be a process that already exited.*/
  bool is_open() const;

  // Asynchronously wait for the process to exit and deliver the native exit-code in the completion handler.
  template <BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void (error_code, int))
  WaitHandler = net::default_completion_token_t<executor_type>>
  auto async_wait(WaitHandler && handler = net::default_completion_token_t<executor_type>());
};

// Process with the default executor.
typedef basic_process<> process;

process_handle.hpp

进程句柄是进程的非托管版本。这意味着它在销毁时不会终止进程,也不会跟踪退出码。

退出码可能会在早期被发现,在调用 running 时。因此,只能发现进程已退出。
template<typename Executor = net::any_io_executor>
struct basic_process_handle
{
    // The native handle of the process.
    /* This might be undefined on posix systems that only support signals */
    using native_handle_type = implementation_defined;

    // The executor_type of the process_handle
    using executor_type =  Executor;

    // Getter for the executor
    executor_type get_executor();

    // Rebinds the process_handle to another executor.
    template<typename Executor1>
    struct rebind_executor
    {
        // The socket type when rebound to the specified executor.
        typedef basic_process_handle<Executor1> other;
    };


    // Construct a basic_process_handle from an execution_context.
    /*
    * @tparam ExecutionContext The context must fulfill the asio::execution_context requirements
    */
    template<typename ExecutionContext>
    basic_process_handle(ExecutionContext &context);

    // Construct an empty process_handle from an executor.
    basic_process_handle(executor_type executor);

    // Construct an empty process_handle from an executor and bind it to a pid.
    /* On NON-linux posix systems this call is not able to obtain a file-descriptor and will thus
     * rely on signals.
     */
    basic_process_handle(executor_type executor, pid_type pid);

    // Construct an empty process_handle from an executor and bind it to a pid and the native-handle
    /* On some non-linux posix systems this overload is not present.
     */
    basic_process_handle(executor_type executor, pid_type pid, native_handle_type process_handle);

    // Move construct and rebind the executor.
    template<typename Executor1>
    basic_process_handle(basic_process_handle<Executor1> &&handle);

    // Get the id of the process
    pid_type id() const
    { return pid_; }

    // Terminate the process if it's still running and ignore the result
    void terminate_if_running(error_code &);

    // Throwing @overload void terminate_if_running(error_code & ec;
    void terminate_if_running();
    // wait for the process to exit and store the exit code in exit_status.
    void wait(native_exit_code_type &exit_status, error_code &ec);
    // Throwing @overload wait(native_exit_code_type &exit_code, error_code & ec)
    void wait(native_exit_code_type &exit_status);

    // Sends the process a signal to ask for an interrupt, which the process may interpret as a shutdown.
    /* Maybe be ignored by the subprocess. */
    void interrupt(error_code &ec);

    // Throwing @overload void interrupt()
    void interrupt();

    // Sends the process a signal to ask for a graceful shutdown. Maybe be ignored by the subprocess.
    void request_exit(error_code &ec);

    // Throwing @overload void request_exit(error_code & ec)
    void request_exit()

    // Unconditionally terminates the process and stores the exit code in exit_status.
    void terminate(native_exit_code_type &exit_status, error_code &ec);\
    // Throwing @overload void terminate(native_exit_code_type &exit_code, error_code & ec)
    void terminate(native_exit_code_type &exit_status);/

    // Checks if the current process is running.
    /*If it has already completed, it assigns the exit code to `exit_code`.
     */
    bool running(native_exit_code_type &exit_code, error_code &ec);
    // Throwing @overload bool running(native_exit_code_type &exit_code, error_code & ec)
    bool running(native_exit_code_type &exit_code);

    // Check if the process handle is referring to an existing process.
    bool is_open() const;

    // Asynchronously wait for the process to exit and assign the native exit-code to the reference.
    // The exit_status can indicate that a process has already be waited for, e.g. when terminate is called.
    template<BOOST_PROCESS_V2_COMPLETION_TOKEN_FOR(void(error_code))
             WaitHandler = net::default_completion_token_t<executor_type>>
    auto async_wait(native_exit_code_type &exit_status, WaitHandler &&handler = net::default_completion_token_t<executor_type>());
};

shell.hpp

这个实用类将命令行解析为令牌,并允许用户根据文本输入执行进程。

在 v1 中,这可以直接在启动进程时完成,但已移除,因为它存在安全风险。

通过显式解析 shell,它鼓励用户在启动可执行文件之前对其进行健全性检查。

示例
asio::io_context ctx;

auto cmd = shell("my-app --help");
auto exe = cmd.exe();
check_if_malicious(exe);

process proc{ctx, exe, cmd.args()};
/// Utility to parse commands
struct shell
{
  shell() = default;
  template<typename Char, typename Traits>
  shell(basic_string_view<Char, Traits> input);

  shell(basic_cstring_ref<char_type> input);
  shell(const shell &) = delete;
  shell(shell && lhs) noexcept;
  shell& operator=(const shell &) = delete;
  shell& operator=(shell && lhs) noexcept;


  // the length of the parsed shell, including the executable
  int argc() const ;
  char_type** argv() const;

  char_type** begin() const;
  char_type** end()   const;

  bool empty() const;
  std::size_t size() const;

  // Native representation of the arguments to be used - excluding the executable
  args_type args() const;
  template<typename Environment = environment::current_view>
  filesystem::path exe(Environment && env = environment::current()) const;
};

start_dir.hpp

/// Initializer for the starting directory of a subprocess to be launched.
struct process_start_dir
{
  filesystem::path start_dir;

  process_start_dir(filesystem::path start_dir);
  {
  }
};

stdio.hpp

 The initializer for the stdio of a subprocess
The subprocess stdio initializer has three members:
  • in 用于 stdin

  • out 用于 stdout

  • err 用于 stderr

如果初始化器存在,则所有三个都将为子进程设置。默认情况下,它们将继承父进程的标准输入输出句柄。这意味着这将把 stdio 转发给子进程。

asio::io_context ctx;
v2::process proc(ctx, "/bin/bash", {}, v2::process_stdio{});

未提供构造函数,以便支持 C++ 稍后版本的指定初始化器。

asio::io_context ctx;

/// C++17
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{.err=nullptr});
/// C++11 & C++14
v2::process proc17(ctx, "/bin/bash", {}, v2::process_stdio{ {}, {}, nullptr});

任何 stdio 的有效初始化器为

  • std::nullptr_t 分配一个 null 设备

  • FILE* 任何打开的文件,包括 stdinstdoutstderr

  • native_handle 任何原生文件句柄(Windows 上的 HANDLE)或文件描述符(Posix 上的 int

  • 任何具有兼容上述 .native_handle() 函数的 io 对象。例如,一个 asio::ip::tcp::socket 或一个 pipe 对象。

  • 一个 filesystem::path,它将根据流的方向打开一个可读或可写文件。

  • 一个 asio::basic_writeable_pipe 用于 stdin 或 asio::basic_readable_pipe 用于 stderr/stdout。

当传递 FILE*native_handle 或带有 native_handle 的 io 对象时,初始化器将按原样将句柄分配给子进程。也就是说,文件描述符/句柄会被克隆到子进程中并未经修改地使用。

当传递 filesystem::path 时,初始化器将尝试打开文件,然后将句柄传递给子进程。

当通过引用将 readable_pipe 传递给 stdout/stderr 或将 writable_pipe 传递给 stdin 时,初始化器将创建管道的另一端(stdout/stderr 的 writable_pipestdinreadable_pipe),连接该对,并将 native_handle 传递给子进程。

也就是说,这两者是等价的

可读管道的隐式构造。
asio::io_context ctx;
asio::writable_pipe wp{ctx};
// create a readable pipe internally and connect it to wp
process proc{ctx, "/bin/bash", {}, process_stdio{.in=wp}};

// create it explicitly
{
  // the pipe the child process reads from
  asio::readable_pipe rp{ctx};
  asio::connect_pipe(rp, wp);
  // `rp.native_handle()`  will be assigned to the child processes stdin
  process proc{ctx, "/bin/bash", {}, process_stdio{.in=rp}};
  rp.close(); // close it so the pipe closes when the `proc exits.
}

显式版本允许您将同一个 writable_pipe 分配给 stdoutstderr

// the pipe the parent process reads from and both
// stderr & stdout of the child process write to
asio::readable_pipe rp{ctx};
asio::writable_pipe wp{ctx};
asio::connect_pipe(rp, wp);
process proc{ctx, "/bin/bash", {}, process_stdio{.out=wp, .err=wp}};
wp.close(); // close it so the pipe closes when the `proc exits.
如果子进程写入管道,则父进程从中读取,反之亦然。
/// The initializer for the stdio of a subprocess
struct process_stdio
{
  __implementation_defined__ in;
  __implementation_defined__ out;
  __implementation_defined__ err;
};

ext

process/ext 中的头文件提供了有关第三方进程的信息。

// Get the cmd line used to launche the process
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
shell cmd(basic_process_handle<Executor> & handle);
shell cmd(pid_type pid, error_code & ec);
shell cmd(pid_type pid);


// Get the current working directory of the process.
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path cwd(basic_process_handle<Executor> & handle)
filesystem::path cwd(pid_type pid, error_code & ec);
filesystem::path cwd(pid_type pid);


// Get the current environment of the process.
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
env_view cwd(basic_process_handle<Executor> & handle)
env_view env(pid_type pid, error_code & ec);
env_view env(pid_type pid);


// Get the executable of the process.
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle, error_code & ec);
template<typename Executor>
filesystem::path exe(basic_process_handle<Executor> & handle)
filesystem::path exe(pid_type pid, error_code & ec);
filesystem::path exe(pid_type pid);
在某些小众平台上,该函数可能会因“operation_not_supported”而失败。
在 Windows 上,还提供了接受 HANDLE 的重载。

posix/bind_fd.hpp

bind_fd 是一个实用类,用于将文件描述符显式绑定到子进程的文件描述符。

struct bind_fd
{
  // Inherit file descriptor with the same value.
  /*
  * This will pass descriptor 42 as 42 to the child process:
  * @code
  * process p{"test", {},  posix::bind_fd(42)};
  * @endcode
  */
  bind_fd(int target);

  // Inherit an asio io-object as a given file descriptor to the child process.
  /*
  * This will pass the tcp::socket, as 42 to the child process:
  * @code
  * extern tcp::socket sock;
  * process p{"test", {},  posix::bind_fd(42, sock)};
  * @endcode
  */

  template<typename Stream>
  bind_fd(int target, Stream && str);

  // Inherit a `FILE` as a given file descriptor to the child process.
  /* This will pass the given `FILE*`, as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, stderr)};

   */
  bind_fd(int target, FILE * f);

  // Inherit a file descriptor with as a different value.
  /* This will pass 24 as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, 24)};

   */
  bind_fd(int target, int fd):

  // Inherit a null device as a set descriptor.
  /* This will a null device as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, nullptr)};

   */
  bind_fd(int target, std::nullptr_t);

  // Inherit a newly opened-file as a set descriptor.
  /* This will pass a descriptor to "extra-output.txt" as 42 to the child process:

       process p{"test", {},  posix::bind_fd(42, "extra-output.txt")};

   */
  bind_fd(int target, const filesystem::path & pth, int flags = O_RDWR | O_CREAT);

};

使用 bind_fd 可以显式继承文件描述符,因为没有未使用的文件描述符会被继承。

windows/creation_flags.hpp

Creation flags 允许显式设置 dwFlags

// An initializer to add to the dwFlags in the startup-info
template<DWORD Flags>
struct process_creation_flags;

// A flag to create a new process group. Necessary to allow interrupts for the subprocess.
constexpr static process_creation_flags<CREATE_NEW_PROCESS_GROUP> create_new_process_group;

// Breakaway from the current job object.
constexpr static process_creation_flags<CREATE_BREAKAWAY_FROM_JOB> create_breakaway_from_job;
// Allocate a new console.
constexpr static process_creation_flags<CREATE_NEW_CONSOLE>        create_new_console;

标志可以这样使用

process p{"C:\\not-a-virus.exe", {}, process::windows::create_new_console};

windows/show_window.hpp

Creation flags 允许显式设置 wShowWindow 选项。

/// A templated initializer to set wShowWindow flags.
template<DWORD Flags>
struct process_show_window;

//Hides the window and activates another window.
constexpr static process_show_window<SW_HIDE           > show_window_hide;
//Activates the window and displays it as a maximized window.
constexpr static process_show_window<SW_SHOWMAXIMIZED  > show_window_maximized;
//Activates the window and displays it as a minimized window.
constexpr static process_show_window<SW_SHOWMINIMIZED  > show_window_minimized;
//Displays the window as a minimized window. This value is similar to `minimized`, except the window is not activated.
constexpr static process_show_window<SW_SHOWMINNOACTIVE> show_window_minimized_not_active;
//Displays a window in its most recent size and position. This value is similar to show_normal`, except that the window is not activated.
constexpr static process_show_window<SW_SHOWNOACTIVATE > show_window_not_active;
//Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.
constexpr static process_show_window<SW_SHOWNORMAL     > show_window_normal;

标志可以这样使用

process p{"C:\\not-a-virus.exe", {}, process::windows::show_window_minimized};

版本 2

Boost.Process V2 是对 Boost.Process 的重新设计,基于之前的设计错误和改进的系统 API。

主要变化是

  • 简化的接口

  • 依赖于 Linux 上的 pidfd_open

  • 完整的 Asio 集成

  • 移除了不可靠的功能

  • UTF8 支持

  • 独立编译

  • 默认文件描述符安全

版本 2 现在是默认版本。为了阻止使用已弃用的 v1,其文档已被删除。

简化的接口

在 v1 进程中,可以在进程的构造函数中定义部分设置,这导致了一个小的 DSL。

child c{exe="test", args+="--help", std_in < null(), env["FOO"] += "BAR"};

虽然一开始看起来很花哨,但它并不能很好地扩展到更多的参数。对于进程 v2,接口很简单。

extern std::unordered_map<std::string, std::string> my_env;
extern asio::io_context ctx;
process proc(ctx, "./test", {"--help"}, process_stdio{nullptr, {}, {}}, process_environment(my_env));

每个初始化器都处理一个逻辑组件(例如 stdio),而不是累积多个组件。此外,每个进程都有一个路径和参数,而不是混乱的 cmd 样式和 exe-args 的混合,这些可以随机分散。

pidfd_open

自 Boost.Process v1 发布以来,Linux 已经发展并添加了 [pidfd_open](https://man7.org/linux/man-pages/man2/pidfd_open.2.html),它允许用户获取进程的描述符。这比 SIGCHLD 更可靠,因为不太容易错过。Windows 一直为进程提供 HANDLE。除非操作系统不支持,否则 Boost.Process v2 将使用文件描述符和句柄来实现进程等待。

完整的 Asio 集成

Process v1 旨在使 asio 可选,但与子进程进行同步 IO 通常意味着很容易导致死锁。由于 asio 在 boost 1.78 中添加了管道,Boost.Process V2 完全基于 asio,并使用其管道和文件句柄与子进程交互。

不可靠的功能

Boost.Process 的某些部分不如它们应有的那样可靠。

这尤其涉及到进程上的 wait_forwait_until 函数。后者在 Windows 上很容易实现,但 Posix 没有提供相应的 API。因此,wait_for 使用信号或 fork,这都不是很安全。由于 Boost.Process v2 基于 Asio,因此支持取消,现在可以通过 async_wait + timeout 安全地实现 wait_for。

UTF-8

Boost.Process V2 不再使用 Windows 上的 ascii API,而是假设所有地方都使用 UTF-8,并使用 UTF-16 API。

默认文件描述符安全

虽然在 Windows 上不是问题(因为 HANDLEs 手动启用了继承),但 Posix 系统默认会创建文件句柄继承问题。

Boost.Process V2 将自动关闭所有非白名单描述符,无需任何选项即可启用。

致谢

第一个 Boost.Process 草案创建于 2006 年。此后,许多人在各种草案上进行了工作。特别是 Merino Vidal、Ilya Sokolov 和 Felipe Tanus 在早期草案上花费了大量时间。多年来,他们影响了 Boost.Process,并编写了在不同程度上仍然存在于库中的代码。

早期版本的 Boost.Process 的设计并不总是令人满意。2011 年,Jeff Flinn 提出了 Boost.Process 今天所依赖的 executor 和 initializer 概念。没有 Jeff 的想法,Boost.Process 的整体设计就不会是今天的样子。

特别感谢 [http://www.intra2net.com/(Intra2net AG)(特别是 Thomas Jarosch),他们赞助了一个项目来支持 Boost.Process 的开发。正是这项赞助使得在 2012 年创建了新的 Boost.Process 版本。

还要特别感谢 Boris Schaeling,尽管 Boost.Process 被拒绝了,但他仍然继续致力于并一直维护它至今,并参与了当前版本的开发。

非常感谢 [https://github.com/samuelvenable](Samuel Venable) 为贡献 [v2::ext] 功能以及所有相关的研究。