调用命令行并获取返回信息是一件很有意思的事情,很多程序都会这么干,比如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()); }
结果如下: