調用命令行並獲取返回信息是一件很有意思的事情,很多程序都會這么干,比如Visual Studio進行編譯時,也是調用了一些命令行程序進行編譯和鏈接,然后將其反饋信息輸出到編譯日志窗口。不過,調用一個命令行程序很簡單,但獲取其反饋信息並不是那么容易。當然如果知道怎么做,也不會很復雜。
思路如下:
- 創建一個匿名管道;
- 調用CreateProcess執行命令行程序並將其輸出定位到匿名管道的寫入端;
- 從匿名管道的讀取端讀取數據,那么讀取到的數據就是命令行程序的輸出信息;
這兒需要解釋一下何謂管道,我的理解是:管道會提供一對端口,當向寫入端口寫入內容時,那么就可以在讀取端口讀取到相同的內容。匿名管道是管道中最簡單的一種,通過調用CreatePipe函數可以創建一組匿名管道。
下面是一個調用命令行程序並獲得返回值的C++函數源碼:
std::string ExeCmd(const char * pszCmd) { //創建匿名管道 SECURITY_ATTRIBUTES sa = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE}; HANDLE hRead, hWrite; if (!CreatePipe(&hRead, &hWrite, &sa, 0)) { return ""; } //設置命令行進程啟動信息(以隱藏方式啟動命令並定位其輸出到hWrite) STARTUPINFO si = {sizeof(STARTUPINFO)}; GetStartupInfo(&si); si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.wShowWindow = SW_HIDE; si.hStdError = hWrite; si.hStdOutput = hWrite; //啟動命令行 PROCESS_INFORMATION pi; if (!CreateProcess(NULL, (char *)pszCmd, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi)) { return ""; } //立即關閉hWrite CloseHandle(hWrite); //讀取命令行返回值 std::string strRet; char buff[1024] = {0}; DWORD dwRead = 0; while (ReadFile(hRead, buff, 1024, &dwRead, NULL)) { strRet.append(buff, dwRead); } CloseHandle(hRead); return strRet; }
這兒需要說明的是:為什么需要在啟動命令行之后,會需要立即關閉hWrite?
原因是這樣的:
首先,hWrite是Windows內核對象,內核對象有個特點,只有所有使用者都關閉該內核對象后,該內核對象才會真正關閉。這兒hWrite已經被上面的進程所使用了,所以此處關閉hWrite並不會導致該句柄失效。
其次,這樣做有個好處在於,但命令行程序結束后,hWrite就會隨之真正關閉。這樣的話,才會讓后續的ReadFile函數能夠結束——否則它會一直等待直到hWrite被關閉(由於ReadFile是個同步函數,如果上面不關閉,以后永遠沒有關閉機會了)。
我在Visual Studio中,用一個對話框程序,簡單測試一下這個函數:
void CTestPipeDlg::OnBnClickedOk() { AfxMessageBox(ExeCmd("ping baidu.com").c_str()); }
結果如下: