1. 背景
有次遇到過這樣的需求:應用程式A(自己可編碼編譯)調用一個控制台程式B(第三方的,不能修改),控制台程式B會在標準輸出裝置(類似命令列視窗)上列印出資料,應用程式A要擷取這些資料,並對資料進行處理。
實現了該需求之後,總結了下實現的方法:“管道”和“重新導向”。這裡簡要總結下“管道”的概念,重點介紹“匿名管道”。
2. 管道(pipe)
管道(pipe)有兩種:匿名管道(anonymous pipes)和具名管道(named pipes)。管道實際是用於處理序間通訊的一段共用記憶體,建立管道的進程稱為管道伺服器,串連到一個管道的進程為管道客戶機。一個進程在向管道寫入資料後,另一進程就可以從管道的另一端將其讀取出來。
匿名管道(Anonymous Pipes)是在父進程和子進程間單向傳輸資料的一種未命名的管道,只能在本機電腦中使用,而不可用於網路間的通訊。
3. 匿名管道(anonymous pipes)
匿名管道由CreatePipe函數建立,該函數在建立匿名管道的同時返回兩個控制代碼:管道唯讀控制代碼和管道唯寫控制代碼。CreatePipe的函數原型為:
BOOL CreatePipe( PHANDLE hReadPipe, // pointer to read handle PHANDLE hWritePipe, // pointer to write handle LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes DWORD nSize // pipe size ); |
通過hReadPipe和hWritePipe所指向的控制代碼可分別以唯讀、唯寫的方式去訪問管道。在使用匿名管道通訊時,伺服器處理序必須將其中的一個控制代碼傳送給客戶機進程。控制代碼的傳遞多通過繼承來完成,伺服器處理序也允許這些控制代碼為子進程所繼承。除此之外,進程也可以通過諸如DDE或共用記憶體等形式的處理序間通訊將控制代碼發送給與其不相關聯的進程。
在用WriteFile函數向管道寫入資料時,只有在向管道寫完指定位元組的資料後或是在有錯誤發生時函數才會返回。如管道緩衝已滿而資料還沒有寫完,WriteFile將要等到另一進程對管道中資料讀取以釋放出更多可用空間後才能夠返回。管道伺服器在調用CreatePipe建立管道時以參數nSize對管道的緩衝大小作了設定。
匿名管道並不支援非同步讀、寫操作,這也就意味著不能在匿名管道中使用ReadFileEx和WriteFileEx,而且ReadFile和WriteFile中的lpOverLapped參數也將被忽略。匿名管道將在讀、寫控制代碼都被關閉後退出,也可以在進程中調用CloseHandle函數來關閉此控制代碼。
4. 管道控制代碼的繼承性
管道伺服器可以通過以下途徑來控制匿名管道是否可以被繼承:
◆在調用CreatePipe函數時,如果管道伺服器將lpPipeAttributes 指向的SECURITY_ATTRIBUTES資料結構的資料成員bInheritHandle設定為TRUE,那麼CreatePipe建立的管道讀、寫控制代碼將會被繼承。
◆管道伺服器可調用DuplicateHandle函數改變管道控制代碼的繼承。管道伺服器可以為一個可繼承的管道控制代碼建立一個不可繼承的副本或是為一個不可繼承的管道控制代碼建立一個可繼承的副本。
◆CreateProcess函數還可以使管道伺服器有能力決定子進程對其可繼承控制代碼是全部繼承還是不繼承。
在產生子進程之前,父進程首先調用Win32 API SetStdHandle使子進程、父進程可共用標準輸入、標準輸出和標準錯誤控制代碼。當父進程向子進程發送資料時,用SetStdHandle將管道的讀控制代碼賦予標準輸入控制代碼;在從子進程接收資料時,則用SetStdHandle將管道的寫控制代碼賦予標準輸出(或標準錯誤)控制代碼。然後,父進程可以調用進程建立函數CreateProcess產生子進程。如果父進程要發送資料到子進程,父進程可調用WriteFile將資料寫入到管道(傳遞管道寫控制代碼給函數),子進程則調用GetStdHandle取得管道的讀控制代碼,將該控制代碼傳入ReadFile後從管道讀取資料。
如果是父進程從子進程讀取資料,那麼由子進程調用GetStdHandle取得管道的寫入控制代碼,並調用WriteFile將資料寫入到管道。然後,父進程調用ReadFile從管道讀取出資料(傳遞管道讀控制代碼給函數)。
When all write handles to the pipe are closed, the ReadFile function returns zero. It is important for the parent process to close its handle to the write end of the pipe before calling ReadFile. If this is not done, the ReadFile operation cannot return zero because the parent process has an open handle to the write end of the pipe.
The procedure for redirecting the standard input handle is similar to that for redirecting the standard output handle, except that the pipe's read handle is used as the child's standard input handle. In this case, the parent process must ensure that the child process does not inherit the pipe's write handle. If this is not done, the ReadFile operation performed by the child process cannot return zero because the child process has an open handle to the write end of the pipe.
5. 參考
以上內容主要參考MSDN協助文檔:MSDN Library Visual Studio 6.0 (CHS)。可以找到CreatePipe函數後,在菜單“查看”>“在目錄中定位主題”,找到管道的詳細說明。同時MSDN也提供了源碼例子。