很多時候,我們的程序需要調用DOS命令,通過Dos命令調用其他程序從而完成所需要完成的功能。比如,調用Dos程序PKZIP完成ZIP包的解壓縮,調用SVN完成文件的更新或者上傳。但是在程序運行時又要求沒有DOS控制台的窗口出現,而且一切本應該在DOS下顯示的信息都應該出現在我們程序提供的文本框里。
如果才能實現這種功能?需要解決兩個問題:
1、調用外部應用程序。
2、不顯示DOS窗口,並能將應該在DOS顯示的內容,重定向到自己程序內。
C++調用外部應用程序有三個SDK:
WinExec,ShellExecute ,CreateProcess
.Net調用外部應用程序用System.Diagnostics.Process.Start(processStartInfo)
其中以WinExec最為簡單,ShellExecute比WinExec靈活一些,CreateProcess最為復雜。
(1)WinExec 兩個參數,前一個指定路徑,后一個指定顯示方式。
(2)ShellExecute 可以指定工作目錄,並且還可以尋找文件的關聯直接打開不用加載與文件關聯的應用程序,ShellExecute還可以打開網頁,啟動相應的郵件關聯發送郵件等等。
(3)CreateProcess 一共有十個參數,不過大部分都可以用NULL代替,它可以指定進程的安全屬性,繼承信息,類的優先級等等。如果我們要得到足夠多的關於新的進程的信息,控制新的進程的細節屬性,若要達到這些目的,我們就需要使用CreateProcess函數了。
要實現[將應該在DOS顯示的內容,重定向到自己程序內],本程序選了CreateProcess。
調用外部應用程序的SDK確定了之后,還需要考慮用什么方法重定向DOS的顯示信息,此時,可以用[匿名管道] 技術實現這個功能。
管道
管道技術由來已久,相信不少人對DOS命令里的管道技術最為熟悉。當我們type一個文件的時候如果想讓他分頁現實可以輸入
C:\>type autoexec.bat|more
這里“|”就是管道操作符。他以type輸出的信息為讀取端,以more的輸入端為寫入端建立的管道。
管道是針對兩個進程之間的通信而設計的,管道建立后,實際獲得兩個文件描述符:一個用於讀取而另外一個用於寫入。任何從管道寫入端寫入的數據,可以從管道讀取端讀出。特點:管道是半雙工的,數據只能向一個方向流動,需要雙方通信時,需要建立起兩個管道。
匿名管道
匿名管道,就是沒有名字的管道,還有一種管道,叫做命名管道。命名管道的功能很強大,匿名管道在命名管道面前,功能那是簡陋的不行的。匿名管道正因為提供的功能很單一,所以它所需要的系統的開銷也就比命名管道小很多,在本地機器上可以使用匿名管道來實現父進程和子進程之間的通信,這里需要注意兩點,第一就是在本地機器上,這是因為匿名管道不支持跨網絡之間的兩個進程之間的通信,第二就是實現的是父進程和子進程之間的通信,而不是任意的兩個進程。然后得話還順便介紹匿名管道的另外一種功能,其通過匿名管道可以實現子進程輸出的重定向。下面介紹一下匿名管道的使用:
(1)匿名管道主要用於本地父進程和子進程之間的通信,
(2)在父進程中的話,首先是要創建一個匿名管道,
(3)在創建匿名管道成功后,可以獲取到對這個匿名管道的讀寫句柄,
(4)然后父進程就可以向這個匿名管道中寫入數據和讀取數據了,
(5)但是如果要實現的是父子進程通信的話,那么還必須在父進程中創建一個子進程,
(6)同時,這個子進程必須能夠繼承和使用父進程的一些公開的句柄,
(7)為什么呢?
(8)因為在子進程中必須要使用父進程創建的匿名管道的讀寫句柄,
(9)通過這個匿名管道才能實現父子進程的通信,所以必須繼承父進程的公開句柄。
(10)同時在創建子進程的時候,
(11)必須將子進程的標准輸入句柄設置為父進程中創建匿名管道時得到的讀管道句柄,
(12)將子進程的標准輸出句柄設置為父進程中創建匿名管道時得到的寫管道句柄。
(13)然后在子進程就可以讀寫匿名管道了。
借用Linux下的匿名管道實現機制如下:
要實現全雙工通信,就需要再創建一個管道,並且Windows是子進程是將標准輸入輸出重置為管道讀寫端的。
調用外部程序步驟
1、創建管道
函數原型:
BOOL WINAPI CreatePipe(
_Out_PHANDLE hReadPipe,
_Out_PHANDLE hWritePipe,
_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,
_In_DWORD nSize);
實際調用形式:
CreatePipe(&read, &write, &sa, 0);
其中read是讀句柄,write是寫句柄,sa是管道安全屬性,0代表管道緩沖設置為系統默認值。
由上函數可知在創建管道之前,需要先設置管道安全屬性。
設置管道安全屬性
對象原型:
typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; //結構體的大小,可用SIZEOF取得 LPVOID lpSecurityDescriptor; //安全描述符 BOOL bInheritHandle ;//安全描述的對象能否被新創建的進程繼承 } SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;
在程序中僅需如下設置即可:(網上有很多使用方法,此處略過)
sa.bInheritHandle = TRUE; // TRUE為管道可以被子進程所繼承 sa.lpSecurityDescriptor = NULL; // 默認為NULL sa.nLength = sizeof(SECURITY_ATTRIBUTES);
創建好管道后,可以考慮創建子進程,使其繼承父進程的管道句柄。
創建子進程
1、createprocess的參數
TCHAR szCmdline[] = TEXT("../../child/Debug/child.exe"); // 設置子進程路徑
BOOL bSuccess = FALSE;
PROCESS_INFORMATION pi; // 用來接收新進程的識別信息 STARTUPINFO si; // 用於決定新進程的主窗體如何顯示 // 設置PROCESS_INFORMATION ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); // 用0填充內存區域 // 設置STARTUPINFO ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); // 結構大小
HANDLE read1, write1,read2,write2;
//*************** 句柄繼承設置****************** // 創建了兩個管道 // 管道1由父進程讀,子進程寫 // 管道2由父進程寫,子進程讀 si.hStdError = write1; // 錯誤輸出句柄(在寫句柄中寫回父進程) si.hStdOutput = write1; // 子進程繼承管道1寫句柄 si.hStdInput = read2; // 子進程繼承管道2讀句柄 //*************** 句柄繼承設置****************** si.dwFlags |= STARTF_USESTDHANDLES; // 使用hStdInput 、hStdOutput 和hStdError 成員 // 創建子進程 // 摘自msdn: // If lpApplicationName is NULL, // the first white space–delimited token of the command line specifies the module name. bSuccess = CreateProcess( NULL, // lpApplicationName szCmdline, // command line // 以上兩個字段都可以創建目標子進程 NULL, // process security attributes NULL, // primary thread security attributes TRUE, // bInheritHandles:指示新進程是否從調用進程處繼承了句柄 0, // creation flags:指定附加的、用來控制優先類和進程的創建的標志。 // 設置為 CREATE_NEW_CONSOLE 可顯示子窗口 NULL, // use parent's environment NULL, // use parent's current directory &si, // STARTUPINFO :指向一個用於決定新進程的主窗體如何顯示的STARTUPINFO結構體 &pi // PROCESS_INFORMATION :指向一個用來接收新進程的識別信息的PROCESS_INFORMATION結構體 ); // If an error occurs, exit the application. if (!bSuccess) cout << "創建子程序失敗" << endl; else { // 關閉一些子進程用的句柄 CloseHandle(pi.hProcess); CloseHandle(pi.hThread); CloseHandle(write1); CloseHandle(read2); }
首先設置子進程所在路徑,子進程為一個exe可執行程序。然后會用到兩個類型STARTUPINFO和PROCESS_INFORMATION,有興趣的朋友可自行百度,查看兩種類中的參數。
這里也不貼CreateProcess的函數原型了,代碼塊中有較好的注釋。
其實對於管道創建和子進程創建都是一個模版框架。
讀寫函數請見github源代碼
步驟總結:
1、創建管道
2、創建子進程
3、父進程從管道內讀取數據
4、子進程內,從管道內讀取數據,並且向另一個管道內寫入數據,供父進程讀取。(子進程調用一般就是DOS,然后是外部程序的命令以及參數等)