上一篇《進程通訊之二 管道技術第一篇 輸入輸出的重新導向》示範了增加若干程式碼來完成程式輸入輸出的重新導向,並提出了如果沒有程式原始碼,只有程式檔案如何來完成重新導向。本篇就介紹如何使用匿名管道來完成這一任務。
電腦中管道pipe類似於現實世界中的水管道,在一端放入水流,另一端就會流出來。在電腦機中水流自然被資料流所代替了。電腦中管道分為匿名管道和具名管道,本篇將主要介紹用匿名管道來完成這一重新導向輸出任務,具名管道就留給下一篇來介紹了。
先來看看如何建立和使用匿名管道。
第一個 CreatePipe
函數功能:建立管道
函數原型:
BOOLWINAPICreatePipe(
PHANDLEhReadPipe,
PHANDLEhWritePipe,
LPSECURITY_ATTRIBUTESlpPipeAttributes,
DWORDnSize
);
函數說明:
第一個參數返回新建立的管道的讀取端控制代碼。
第二個參數返回新建立的管道的寫入端控制代碼。
注意不能在管道的讀取端寫入資料也不能在寫入端讀取資料。
第三個參數表示管道的安全屬性,通常可以作如下設定:
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
第四個參數表示管道的緩衝區容量,為0表示使用預設大小。
函數執行成功返回TRUE,否則返回FALSE。
第二個 ReadFile
函數功能:從管道中讀取資料
函數原型:
BOOLReadFile(
HANDLEhFile,
LPVOIDlpBuffer,
DWORDnNumberOfBytesToRead,
LPDWORDlpNumberOfBytesRead,
LPOVERLAPPEDlpOverlapped
);
函數說明:
第一個參數為控制代碼,可以是建立檔案函數CreateFile()的傳回值也可以是管道。
第二個參數是一個指向緩衝區的指標,函數將讀取的資料寫入該緩衝區。
第三個參數的表達非常好,光從名字上就可以知道這是用來指定讀取的位元組數。
第四個參數將返回實際讀取到的位元組數。
第五個參數是用於非同步作業方面,一般傳入NULL即可。
第三個 WriteFile
函數功能:向管道寫入資料
函數原型:
BOOLWriteFile(
HANDLEhFile,
LPCVOIDlpBuffer,
DWORDnNumberOfBytesToWrite,
LPDWORDlpNumberOfBytesWritten,
LPOVERLAPPEDlpOverlapped
);
函數說明:
第一個參數為控制代碼,可以是建立檔案函數CreateFile()的傳回值也可以是管道。
第二個參數是一個指標,該指標指向待寫入管道的資料。
第三個參數表示要寫入的位元組數。
第四個參數將返回實際寫入管道的位元組數。
第五個參數是用於非同步作業方面,一般傳入NULL即可。
第四個CloseHandle
函數功能:關閉管道的一端
函數原型:BOOLCloseHandle(HANDLEhObject);
函數說明:當讀取和寫入端都關閉後,系統會關閉管道並回收資源。
從後面三個函數可以看出,向管道中讀取和寫入資料就和向檔案中讀取和寫入資料是一樣的(事實上管道也是一種特殊的檔案——記憶體對應檔)。
使用管道要注意的一個地方是:讀取和寫入資料時,一定要注意順序,MSDN上說,如果管道中沒有資料,調用ReadFile()會造成阻塞,直到有其它線程將資料寫入管道。同樣,當有線程正在管道中讀取資料時,其它試圖將資料寫入管道的的線程也會被阻塞。
因此對上一篇的樣本程式進行重新導向時,可以先建立二個管道,一個用來存放輸入資料,稱為資料輸入管道,另一個用來存放輸出資料,稱為資料輸出管道。然後從輸入檔案中讀取資料並寫入資料輸入管道。再啟動樣本程式作為子進程,子進程的輸入輸出已經改成從資料輸入管道中讀取和輸出到資料輸出管道。子進程運行結束後,從資料輸出管道中將資料寫入到輸出檔案即可。整個流程圖如下所示:
下面給出使用管道的範例程式碼:
//用管道來完成子進程的重新導向。//流程如下:// infile.txt -> Input管道 -> 標準程式.exe -> Output管道 -> outfile.txt#include <windows.h>#include <stdio.h>int main(){printf(" 使用管道來重新導向子進程的輸入輸出\n"); printf(" --by MoreWindows( http://blog.csdn.net/MoreWindows )--\n\n"); char sz[3][50] = {"樣本程式.exe", "infile.txt", "outfile.txt"};HANDLE hPipeInputRead, hPipeInputWrite, hPipeOutputRead, hPipeOutputWrite;//建立兩個管道SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(SECURITY_ATTRIBUTES);sa.lpSecurityDescriptor = NULL;sa.bInheritHandle = TRUE;//資料輸入管道CreatePipe(&hPipeInputRead, &hPipeInputWrite, &sa, 0);//資料輸出管道CreatePipe(&hPipeOutputRead, &hPipeOutputWrite, &sa, 0);printf("建立資料輸入管道和資料輸出管道完畢\n");//從檔案中讀取資料,寫入管道ReadFile中.const int BUFSIZE = 4096;CHAR chBuf[BUFSIZE] = {0}; DWORD dwRead, dwWritten;BOOL fSuccess;HANDLE hInputFile = CreateFile(sz[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);while (true) {//從檔案中讀取資料fSuccess = ReadFile(hInputFile, chBuf, BUFSIZE, &dwRead, NULL);if (!fSuccess || dwRead == 0)break; //將資料寫入管道fSuccess = WriteFile(hPipeInputWrite, chBuf, dwRead, &dwWritten, NULL);if (!fSuccess) break; } //關閉輸入資料管道CloseHandle(hInputFile);hInputFile = NULL;CloseHandle(hPipeInputWrite);hPipeInputWrite = NULL;printf("已經從檔案中讀取資料並寫入資料輸入管道\n");printf("啟動樣本程式並重新導向到管道中\n....\n");//啟動樣本程式作為子進程STARTUPINFO si;si.cb = sizeof(STARTUPINFO);GetStartupInfo(&si); si.hStdInput = hPipeInputRead; //輸入由標準輸入 -> 從管道中讀取si.hStdOutput = hPipeOutputWrite; //輸出由標準輸出 -> 輸出到管道si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; PROCESS_INFORMATION pi; CreateProcess( sz[0], NULL, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi);WaitForSingleObject(pi.hProcess, INFINITE);//關閉輸入資料管道CloseHandle(hPipeInputRead);hPipeInputRead = NULL;CloseHandle(hPipeOutputWrite);hPipeOutputWrite = NULL;printf("樣本程式完成處理,現在將資料輸出管道中的資料寫入檔案\n");//將輸出資料管道中的資料寫入到檔案中HANDLE hOutputFile = CreateFile(sz[2], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); while (true){ //從管道中讀取fSuccess = ReadFile(hPipeOutputRead, chBuf, BUFSIZE, &dwRead, NULL);if( !fSuccess || dwRead == 0) break; //寫入輸出檔案fSuccess = WriteFile(hOutputFile, chBuf, dwRead, &dwWritten, NULL);if (!fSuccess) break; } //關閉輸出資料管道CloseHandle(hOutputFile);hOutputFile = NULL;CloseHandle(hPipeOutputRead);hPipeOutputRead = NULL;printf("資料輸出管道中的資料寫入檔案完畢\n");return 0;}
運行結果如:
結果完全正確,說明我們的程式已經完成了啟動其它程式並對它進行重新導向這一功能。
對匿名管道總結一下:匿名管道有讀取端和寫入端。匿名管道建立(CreatePipe)後就可以像讀寫檔案一樣的對管道中的資料讀寫(ReadFile與WriteFile),但要注意讀寫順序。匿名管道在關閉兩端後會由系統銷毀並回收資源。
匿名管道的使用比較常見,下面是二個安裝程式的。
QQ遊戲的安裝過程:
五筆編碼及時查的安裝過程:
對比下這二個,顯示的內容都差不多,都是解壓縮檔案並移動到指定地方。唯一不同的是一個是控制台介面,另一個是圖形介面。聯想上面的程式,不難得知QQ遊戲的安裝實際也是使用管道將一個控制台程式的輸出內容顯示到圖形介面,這樣既美觀又便於維護。
下一篇《進程通訊之二 管道技術第三篇 具名管道》將介紹具名管道的使用,歡迎參閱。
註:不知道程式碼的情況下還可以使用批處理來完成。批處理使用>和<來重新導向,>為輸出到檔案,如果檔案不存在就建立,已存在就清空原檔案後再寫入,<為從檔案讀取。批次檔的內容可以這樣寫:
@echo off
<infile.txt 標準程式.exe >outfile.txt
也可以這樣寫:
@echo off
標準程式.exe <infile.txt >outfile.txt
批處理重新導向的內部實現原理當然也是使用匿名管道。
轉載請標明出處,原文地址:http://blog.csdn.net/morewindows/article/details/7390441
如果覺得本文對您有協助,請點擊‘頂’支援一下,您的支援是我寫作最大的動力,謝謝。