前面一篇已經把 Boost Process v2 的大部分東西都大概紀錄了一下。而在外部程式的輸入、輸出的部分,Process 也有透過 initializer 的形式來提供多種設定、使用的方法,這邊就獨立一篇出來講吧。
首先,process v2 是提供 process_stdio
這個結構,讓使用者可以針對輸入(in
)、輸出(out
)、錯誤(err
)分別進行設定、決定要處理的方法。
而可以使用的形式,主要包括:
- asio pipe
- C 的檔案指標
FILE*
- 指定檔案路徑
- 忽略輸出的
nullptr
- 個平台原生的 handler
而基本的使用,會是下面的形式:
boost::process::process proc(ctx, "c:\\windows\\system32\\ping.exe", { "www.google.com", "-n", "4" }, boost::process::process_stdio{ {}, {}, {} });
這邊在建立 process_stdio
的物件的時候,給了三個空的參數,分別代表輸入、輸出、錯誤;而像這樣給 {}
就代表是給預設值,所以這邊其實有給和沒給是一樣的。
而如果想要修改輸入、輸出的方法的話,基本上只要修改 process_stdio
建構時的三個參數就可以了。
不過這邊先提一下,Heresy 在測試的時候,是發現 Windows 環境下似乎沒辦法把 out
和 err
設定給同一個目標?這個不知道是不是實作上的限制?
FILE*
如果是要比較容易理解的操作,應該是使用 C 的檔案指標?這邊也支援 stdin
、stdout
、stderr
這些巨集(參考),所以如果只是要做重新導向的話,算是滿方便的。
像是下面這段程式:
boost::process::process proc(ctx, "/usr/bin/ping", { "www.google.com", "-c", "4" }, boost::process::process_stdio{ {}, stdout, stdout });
這邊透過 process_stdio
來讓正常的輸出和錯誤的輸出都丟給 stdout
。
不過這樣的程式雖然在 Linux 雖然可以正確執行,但是就像前面提到的、在 Windows 下似乎是會在執行時出錯的…
而如果是想要把結果輸出到檔案,也可以寫成:
FILE* fp = fopen("/tmp/test.txt", "w+"); boost::asio::io_context ctx; boost::process::process proc(ctx, "/usr/bin/ping", { "www.google.com", "-c", "4" }, boost::process::process_stdio{ {}, fp, fp }); proc.wait(); fclose(fp);
這樣的程式就會把 ping 的結果和錯誤都儲存在 /tmp/test.txt
這個檔案了。
而這邊也可以用讀取模式開啟一個檔案、把它當成標準輸入丟給外部程式,寫法基本上是類似的。
檔案路徑
如果只是要輸出到檔案、或是從檔案讀取的話,其實 process_stdio
也可以直接透過檔案路徑來設定、不需要手動控制 FILE*
;不過,這邊的路徑應該只能用 boost::filesystem::path
就是了。
比如說上面的程式碼可以改寫成:
boost::process::process proc(ctx, "/usr/bin/ping", { "www.google.com", "-c", "4" }, boost::process::process_stdio{ {}, boost::filesystem::path("/tmp/test.txt"), {} });
這樣也是會把結果輸出到檔案裡的。(不過這邊就沒管錯誤了)
忽略輸出
而如果不想管輸出的話,其實也可以直接設定成 nullptr
,這樣就可以直接無視了。
像寫成下面的樣子的話,基本上就不會看到 ping 的結果了。
boost::process::process proc(ctx, "/usr/bin/ping", { "www.google.com", "-c", "4" }, boost::process::process_stdio{ {}, nullptr, {} });
而根據官方文件的說法,這樣寫在 POSIX 系統上會是丟給 /dev/null
、在 Windows 則是丟給 NUL
。
asio pipe
如果要進行比較複雜的操作的話,這邊可能就要使用 Boost asio 的 pipe 了。下面是根據官方的範例,把之前 ping 的範例、修改成用 readable_pipe
讀取的程式;這邊為了讓 Windows 和 Linux 都能執行,其實還滿麻煩的…
#ifdef _WIN32 #include <WinSock2.h> #pragma comment(lib, "ntdll.lib") #pragma comment(lib, "user32.lib") #pragma comment(lib, "shell32.lib") #endif #include <iostream> #include <boost/asio/read.hpp> #include <boost/process.hpp> #ifdef _WIN32 const std::string sPara = "-n"; #else const std::string sPara = "-c"; #endif int main() { boost::asio::io_context ctx; boost::asio::readable_pipe rp{ ctx }; boost::process::process proc(ctx, boost::process::environment::find_executable("ping"), { "www.google.com", sPara, "4" }, boost::process::process_stdio{ {}, rp, {} }); boost::system::error_code ec; std::string output; boost::asio::read(rp, boost::asio::dynamic_buffer(output), ec); if (ec == boost::asio::error::eof || // Linux ec == boost::asio::error::broken_pipe) // Windows { std::cout << output << std::endl; } else { std::cerr << "Error: " << ec.message() << std::endl; } proc.wait(); }
這邊會先建立一個 readable_pipe
的物件 rp
,之後則是讓 process 把本來的標準輸出都丟到 rp
裡面。
在執行後,則是透過 boost::asio_read()
把資料全部讀到 output
這個字串裡面,確認沒問題後再整個輸出。
不過在 Windows 上正常結束的時候,ec
似乎不會是 boost::asio::error::eof
、而都是 boost::asio::error::broken_pipe
?這點不確定是什麼的問題。
不過這樣的讀取方法會一次讀完、所以 ping 的結果會一次讀取到字串後再統一輸出,和本來一次一次輸出會有相當地差別。
這樣的效果在不少使用情境下可能不見得適合,如果不想要這樣,就得用別的讀取函示來逐步讀取了。個人覺得比較直覺的方法,應該會是逐行讀取?但是很可惜的,是 Boost asio 相關的讀取函式似乎沒有類似 getline()
的方法,所以要把輸出的結果一行一行處理其實相對還滿麻煩的。
而如果想要給輸入用的話,則是可以使用 asio 的 writable_pipe
來操作。下面是一個簡單的例子:
boost::asio::io_context ctx; boost::asio::writable_pipe wp{ ctx }; boost::asio::readable_pipe rp{ ctx }; boost::process::process proc(ctx, "./echo", {}, boost::process::process_stdio{ wp, rp, {} }); std::string line; boost::asio::write(wp, boost::asio::buffer("test1\n")); boost::asio::read_until(rp, boost::asio::dynamic_buffer(line), '\n'); std::cout << "echo: [" << line << "]\n"; boost::asio::write(wp, boost::asio::buffer("exit\n"));
這邊是去呼叫一個自己寫的 echo
程式、輸入什麼就會回傳什麼。
而之後則是會送一個「test\n
」的字串,然後讀取他的輸出值後再透過 std::out
輸出;最後則是在透過送出一個「exit\n
」來結束 echo
這個程式。
不過實際上,這邊的程式只是示意、還是有些小問題的,實務上要做到實用還有不少細節要修改就是了。
popen
如果要用 pipe 來控制輸入和輸出的話,除了透過 process_stdio
來設定外,process 還有另外提供一個稍微簡單一點的方法,那就是 popen
。
popen
是一個 process
的擴展型別、使用方法基本上和 process
相同,不過預設就會把標準輸入和標準輸出改掉(沒有修改錯誤的部分),而本身就可以透過 asio 相關的函式還做讀寫,所以算是可以簡化 process
的設定。
以前面 echo
的例子來說,可以改寫成:
boost::asio::io_context ctx; boost::process::popen proc(ctx, "./echo", {}); std::string line; boost::asio::write(proc, boost::asio::buffer("test1\n")); boost::asio::read_until(proc, boost::asio::dynamic_buffer(line), '\n'); std::cout << "echo: [" << line << "]\n"; boost::asio::write(proc, boost::asio::buffer("exit\n"));
這邊基本上就是把 proc
的型別從 process
改成 popen
而已,之後就 asio 的 read、write 等函式就可以直接對 proc
來進行操作了。
這樣的寫法其實和直接使用 asio 的 pipe 沒有什麼差別,除了省去初始化的設定外,個人覺得最大的好處就是不用去處理 pipe 的物件。
Native handle
除了上面的東西外,Process 也可以針對系統原生的 handler 來做處理。在 POSIX 的系統上會是 int
這種 file descriptor,在 Windows 上則是檔案的 HANDLE
。
此外,只要是可以透過 native_handle()
這個函式取得 stdio stream 可用的型別的物件、也都可以搭配 process_stdio
來使用。
不過這邊基本上就先無視這部份了。
Boost Process 就先這樣了。
其實本來還有想要針對 pipe 的操作繼續寫下去,但是後來想想那部份應該算是 asio 的東西,所以就不寫在這一篇了;之後如果有機會,再來研究看看怎麼把 asio 的 pipe 轉換成 io stream 來做存取吧。