Boost C++ 函式庫

...世界上最受推崇且設計精良的 C++ 函式庫專案之一。 Herb SutterAndrei Alexandrescu,《C++ Coding Standards

PrevUpHomeNext

第 29 章 Boost.Process

Klemens David Morgenstern

依據 Boost 軟體授權條款 1.0 版散佈。(請參閱隨附檔案 LICENSE_1_0.txt 或複製於 https://boost.dev.org.tw/LICENSE_1_0.txt)

目錄

Process V1
簡介
概念
教學
設計理念
擴充功能
常見問題
參考
Process V2
簡介
快速入門
啟動器
process_start_dir
stdio
環境
參考
致謝
[Note] 注意事項

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 的共通點。

管道是作業系統提供的一種機制,用於不同執行緒、程序,以及在某些情況下,不同機器之間的通訊。

管道的典型特徵是,它是一個通道,提供兩個控制代碼,一個用於讀取(來源),一個用於寫入(接收端)。在這方面,它與其他機制(例如通訊端)不同,並提供了另一種管理連線的方式:如果管道的一端關閉(即管道斷裂),則另一端會收到通知。

管道通常用於程序間通訊。主要原因是管道可以直接指定給程序的 stdio,即 stderr、stdin 和 stdout。此外,管道的一半可以繼承給子程序,並在父程序中關閉。這將導致子程序結束時管道斷裂。

但請注意,如果同一個執行緒讀取和寫入管道,它只會與自身通訊。

最常見的管道是匿名的。由於它們沒有名稱,因此只能透過複製任一控制代碼來取得它們的控制代碼。

在此函式庫中,下列函式用於建立未命名管道

顧名思義,命名管道具有字串識別碼。這表示也可以使用識別碼取得它們的控制代碼。

在 POSIX 系統上的實作使用 FIFO (先進先出),這表示命名管道表現得像檔案一樣。

Windows 提供了一個稱為 命名管道 的機制,它們也具有類似檔案的名稱,但範圍與實際檔案系統不同。

[Note] 注意事項

命名管道是這個函式庫一部分的主要原因是,它們需要在 Windows 上用於內部的非同步通訊。

程序是一個獨立可執行的實體,它與執行緒不同,因為它擁有自己的資源。這些資源包括記憶體和硬體資源。

每個程序都由一個稱為程序識別碼的唯一號碼[27]pid)來識別。

程序將返回一個整數值,指示它是否成功。在 POSIX 系統上,有更多與之關聯的代碼,但在 Windows 上則不然。因此,目前函式庫中沒有這樣的編碼。然而,結束代碼為零表示程序成功,而非零則表示錯誤。

程序也可以被強制退出。有兩種方法可以做到這一點:發送信號給程序要求其退出並等待,或者直接無條件終止程序。

通常,第一種方法是發送信號請求退出,但與 POSIX 不同,Windows 並未提供一致的方法來執行此操作。因此,這不是函式庫的一部分,只有強制終止是。

環境是每個程序的局部變數映射。對於這個函式庫來說,最重要的變數是 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 樣式,其中第一個字串將被解釋為檔名(包含路徑),其餘字串則作為傳遞給該函數的參數。

[Note] 注意事項

有關 cmd/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");

[Note] 注意事項

search_path 將搜尋具有該名稱的任何可執行檔。這也包含在 Windows 上新增檔案後綴,例如 .exe.bat

鑑於我們的範例使用了 system 函數,我們的程式將會等待子行程完成。這可能並非我們所期望的,尤其是在編譯可能需要一段時間的情況下。

為了避免這種情況,boost.process 提供了幾種啟動行程的方法。除了已提到的 system 函數及其非同步版本 async_system 之外,我們還可以使用方法 spawnchild 類別。

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();

因此,我們透過呼叫 child 建構函數來啟動行程。然後,我們在行程執行時檢查並執行其他操作,之後取得結束程式碼。呼叫 wait 是必要的,以便取得它並告知作業系統,沒有其他人在等待該行程了。

[Note] 注意事項

您也可以使用 wait_forwait_until 等待一段時間或直到某個時間點。

[Warning] 警告

如果您沒有對子行程物件呼叫 wait,它將在解構時被終止。可以透過事先呼叫 detach 來避免這種情況。

到目前為止,我們都假設一切正常,但並非不可能發生「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 (ipstreamopstreampstream) 來包裝 pipe,並提供 std::istreamstd::ostreamstd::iostream 介面的實作。

std::vector<std::string> read_outline(std::string & file)
{
    bp::ipstream is; //reading pipe-stream
    bp::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 重新導向到管道中,我們同步讀取它。

[Note] 注意事項

您可以使用 std_err 執行相同的操作。

現在我們從 nm 獲取名稱,我們可能想要對其進行demangle,所以我們使用輸入和輸出。 nm 有一個 demangle 選項,但為了範例,我們將使用 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++filt
    bp::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();

[Note] 注意事項

boost::asio::io_service 的實例傳遞給啟動函式會自動使其非同步等待退出,因此不需要呼叫 wait

為了更簡化,您可以使用 std::future 進行非同步操作(您仍然需要將 boost::asio::io_service 的參考傳遞給啟動函式,除非您使用 bp::systembp::async_system)。

現在我們將重新審視我們的第一個範例,並非同步讀取編譯器輸出。

boost::asio::boost::asio::io_service ios;

std::future<std::string> data;

child c("g++", "main.cpp", //set the input
        bp::std_in.close(),
        bp::std_out > bp::null, //so it can be written without anything
        bp::std_err > data,
        ios);


ios.run(); //this will actually block until the compiler is finished

auto err =  data.get();

啟動多個行程時,可以將它們分組在一起。如果子行程沒有修改群組成員資格,這也適用於啟動其他行程的子行程。例如,如果您呼叫 make 來啟動其他行程,並對其呼叫 terminate,它不會終止子行程的所有子行程,除非您使用群組。

使用群組的兩個主要原因是:

  1. 能夠終止子行程的子行程。
  2. 將多個行程分組成一個,以便可以一次終止它們。

如果我們有一個像 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 process
bp::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"});

在下一節中,將描述可用的風格。請注意,多載風格是透過型別特徵實現的,因此將列出這些型別。

[Caution] 注意

引數的應用順序沒有保證!然而,屬於同組的引數,例如字串引數和 args 屬性,保證會按照給定的順序進行評估。

將引數傳遞給行程時,提供兩種樣式:cmd 樣式和 exe-/args 樣式。

cmd 樣式會將字串解譯為 exe 和引數的序列,並照此解析它們,而 exe-/args 樣式會將每個字串解譯為一個引數。

表 29.1. Cmd 與 Exe/Args 的比較

字串

Cmd

Exe/Args

"gcc --version"

{"gcc", "--version"}

{"\"gcc --version\""}


使用重載變體時,單個字串會導致 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-

[Note] 注意事項

如果在引數樣式中使用 '"' 符號,它將作為引數的一部分傳遞。如果想在 cmd 語法中達到相同的效果,則應將其跳脫,例如 '\"'。

[Note] 注意事項

在命令樣式中會自動搜尋 PATH 變數,但搜尋的是啟動行程的變數,而不是傳遞給子行程的變數。

擴充功能最簡單的形式是提供另一個處理器,它將在行程啟動時的各個事件上被呼叫。名稱如下:

  • boost::process::v1::on_setup
  • boost::process::v1::on_error
  • boost::process::v1::on_success

例如:

child c("ls", on_setup([](){cout << "On Setup" << endl;}));

[Note] 注意事項

在 POSIX 系統上,所有這些回呼都將由這個行程處理,而不是由建立的行程處理。這與 POSIX 擴充功能不同,POSIX 擴充功能可以在分岔的行程上執行。

為了擴充程式庫,提供了標頭檔 boost/process/extend.hpp

它只提供自訂屬性的顯式樣式,而不提供隱式樣式。

這表示可以實作一個自訂的初始化器,可以將其參考傳遞給其中一個啟動函式。如果一個類繼承了 boost::process::v1::extend::handler,它將被視為一個初始化器,因此直接放入執行器所傳遞的序列中。

執行器在行程啟動期間呼叫初始化器的不同處理器。基本結構由三個函式組成,如下所示:

此外,POSIX 還提供了三個處理器,如下所示:

更多資訊,請參閱 posix_executor 的參考。

最簡單的擴充功能只需要一個處理器,這可以用函式式風格來完成。讓我們從一個簡單的 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_executorposix_executor

[Note] 注意事項

結合 on_exit,也可以處理程序退出。

[Caution] 注意

posix 處理程序符號在 Windows 上未定義。

由於前面的示例採用函數式風格,因此不太容易重複使用。為了 解決這個問題,handler 在 boost::process::v1::extend 命名空間中有一個別名,可以被繼承。所以讓我們用一個類別來實現 hello world 示例。

struct hello_world : handler
{
    template<typename Executor>
    void ex::on_success(Executor & exec) const
    {
        std::cout << "hello world" << std::endl;
    }
};

//in our function
child c("foo", hello_world());

[Note] 注意事項

實作是透過多載(overloading),而不是覆寫(overriding)。

每個未實作的處理程序都預設為 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
    }
};

[Note] 注意事項

繼承 require_io_context 是必要的,所以 system 提供了一個。

此外,處理程序可以提供一個在子程序退出時被呼叫的函數。這是透過 ex::async_handler 完成的。

[Note] 注意事項

async_handler 隱含了 require_io_context

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;
               };

    }
};

[Caution] 注意

on_exit_handler 沒有預設值,並且在繼承 async_handler 時始終是必需的。

[Caution] 注意

on_exit_handler 使用 boost::asio::signal_set 在 posix 上監聽 SIGCHLD。應用程式不得使用 signal()sigaction() 等函數也註冊 SIGCHLD 的訊號處理程序(但使用 boost::asio::signal_set 則是可以的)。

如果在初始化程序中發生錯誤,應將其告知執行器,而不是直接處理。這是因為行為可以透過傳遞給啟動函數的參數來更改。因此,執行器具有函數 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 上的 charwchar_t。一種解決方案是使用 BOOST_WINDOWS_API 和 BOOST_POSIX_API 巨集,只要包含任何 process 標頭,這些巨集就會自動可用。

另一種變體是類型別名 ex::posix_executorex::windows_executor,其中非當前系統上的執行器是前向聲明。這可以正常工作,因為該函數永遠不會被調用。因此,讓我們實現另一個範例,它會印出可執行檔名稱 ex::on_success

struct hello_exe : handler
{
    template<typename Sequence>
    void ex::on_success(ex::posix_executor<Sequence> & exec)
    {
        std::cout << "posix-exe: " << exec.exe << std::endl;
    }

    template<typename Sequence>
    void ex::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>
    void ex::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;
    }

};

因此,根據我們的範例,使用非原生執行器的定義仍然是一個模板,以便在不使用它們時不會被評估。因此,這提供了一種在不使用預處理器的情況下實現系統特定程式碼的方法。

[Note] 注意事項

如果您只編寫部分實現,例如僅針對 ex::posix_executor,其他變體將預設為 handler

.

現在讓我們重新審視我們的 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 仍會尋找數據。

[Note] 注意事項

此程式庫無法使用自動管道關閉,因為管道可能是檔案控制代碼(例如 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 is void(Exec &), where Exec is a template parameter. 
        unspecified on_error;        // This handler is invoked if an error occurred. The required signature is void(auto & exec, const std::error_code&), where Exec is a template parameter. 
        unspecified on_success;        // This handler is invoked if launching the process has succeeded. The required signature is void(auto & exec), where Exec is a template parameter. 
        unspecified on_fork_error;        // This handler is invoked if the fork failed. The required signature is void(auto & exec), where Exec is a template parameter. 
        unspecified on_exec_setup;        // This handler is invoked if the fork succeeded. The required signature is void(Exec &), where Exec is a template parameter. 
        unspecified on_exec_error;        // This handler is invoked if the exec call errored. The required signature is void(auto & exec), where Exec 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 &&...);
    }
  }
}


[27] 只要行程處於活動狀態,它就是唯一的

[28] 特定領域嵌入式語言 (DSL)


PrevUpHomeNext