什么是管道?參考《WIN32匯編編程》是這樣描述的
Windows 引入了多進程和多線程機制。同時也提供了多個進程之間的通信手段,包括剪貼板、DDE、OLE、管道等,和其他通信手段相比,管道有它自己的限制和特點,管道實際上是一段共享內存區,進程把共享消息放在那里。并通過一些 API 提供信息交換。
管道是兩個頭的東西,每個頭各連接一個進程或者同一個進程的不同代碼,按照管道的類別分有兩種管道,匿名的和命名的;按照管道的傳輸方向分也可以分成兩種,單向的雙向的。根據管道的特點,命名管道通常用在網絡環境下不同計算機上運行的進程之間的通信(當然也可以用在同一臺機的不同進程中)它可以是單向或雙向的;而匿名管道只能用在同一臺計算機中,它只能是單向的。匿名管道其實是通過用給了一個指定名字的有名管道來實現的。
使用管道的好處在于:讀寫它使用的是對文件操作的 api,結果操作管道就和操作文件一樣。即使你在不同的計算機之間用命名管道來通信,你也不必了解和自己去實現網絡間通信的具體細節。
使用匿名管道的步驟如下:
使用 CreatePipe 建立兩個管道,得到管道句柄,一個用來輸入,一個用來輸出
準備執行控制臺子進程,首先使用 GetStartupInfo 得到 StartupInfo
使用第一個管道句柄代替 StartupInfo 中的 hStdInput,第二個代替 hStdOutput、hStdError,即標準輸入、輸出、錯誤句柄
使用 CreateProcess 執行子進程,這樣建立的子進程輸入和輸出就被定向到管道中
父進程通過 ReadFile 讀第二個管道來獲得子進程的輸出,通過 WriteFile 寫第一個管道來將輸入寫到子進程
父進程可以通過 PeekNamedPipe 來查詢子進程有沒有輸出
子進程結束后,要通過 CloseHandle 來關閉兩個管道。
下面是具體的說明和定義:
1. 建立匿名管道使用 CreatePipe 原形如下:
BOOL CreatePipe(
PHANDLE hReadPipe, // address of variable for read handle
PHANDLE hWritePipe, // address of variable for write handle
LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes
DWORD nSize // number of bytes reserved for pipe
);
當管道建立后,結構中指向的 hReadPipe 和 hWritePipe 可用來讀寫管道,當然由于匿名管道是單向的,你只能使用其中的一個句柄,參數中的 SECURITY_ATTRIBUTES 的結構必須填寫,定義如下:typedef struct_SECURITY_ATTRIBUTES{
DWORD nLength: //定義以字節為單位的此結構的長度
LPVOID lpSecurityDescriptor; //指向控制這個對象共享的安全描述符,如果為NULL這個對象將被分配一個缺省的安全描述
BOOL bInheritHandle; //當一個新過程被創建時,定義其返回是否是繼承的.供系統API函數使用.
}SECURITY_ATTRIBUTES;
2. 填寫創建子進程用的 STARTUPINFO 結構,一般我們可以先用 GetStartupInfo 來填寫一個缺省的結構,然后改動我們用得到的地方,它們是:
hStdInput -- 用其中一個管道的 hWritePipe 代替
hStdOutput、hStdError -- 用另一個管道的 hReadPipe 代替
dwFlags -- 設置為 STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW 表示輸入輸出句柄及 wShowWindow 字段有效
wShowWindow -- 設置為 SW_HIDE,這樣子進程執行時不顯示窗口。
填寫好以后,就可以用 CreateProcess 來執行子進程了。
3. 在程序中可以用 PeekNamedPipe 查詢子進程有沒有輸出,原形如下:
OOL PeekNamedPipe(HANDLE hNamedPipe, // handle to pipe to copy from
LPVOID lpBuffer, // pointer to data buffer
DWORD nBufferSize, // size, in bytes, of data buffer
LPDWORD lpBytesRead, // pointer to number of bytes read
LPDWORD lpTotalBytesAvail, // pointer to total number of bytes available
LPDWORD lpBytesLeftThisMessage // pointer to unread bytes in this message );
我們可以將嘗試讀取 nBuffersize 大小的數據,然后可以通過返回的 BytesRead 得到管道中有多少數據,如果不等于零,則表示有數據可以讀取。
4. 用 ReadFile 和 WriteFile 來讀寫管道,它們的參數是完全一樣的,原形如下:
ReadFile or WriteFile(HANDLE hFile, // handle of file to read 在這里使用管道句柄
LPVOID lpBuffer, // address of buffer that receives data 緩沖區地址
DWORD nNumberOfBytesToRead, // number of bytes to read 準備讀寫的字節數
LPDWORD lpNumberOfBytesRead, // address of number of bytes read,實際讀到的或寫入的字節數
LPOVERLAPPED lpOverlapped // address of structure for data 在這里用 NULL);
5. 用 CloseHandle 關閉管道一和管道二的 hReadPipe和 hWritePipe 這四個句柄。
下面是一個演示DEMO,可以使用MEMO來制作一個控制臺,所使用的技術就是管道
procedure RunDosInMemo(Que:String;EnMemo:TMemo);
const
CUANTOBUFFER = 2000;
var
Seguridades : TSecurityAttributes;
PaLeer,PaEscribir : THandle;
start : TStartUpInfo;
ProcessInfo : TProcessInformation;
Buffer : Pchar;
BytesRead : DWord;
CuandoSale : DWord;
begin
//安全描述 可以省略
with Seguridades do
begin
nlength := SizeOf(TSecurityAttributes);
binherithandle := true;
lpsecuritydescriptor := nil;
end;
{Creamos el pipe...}
if Createpipe (PaLeer, PaEscribir, @Seguridades, 0) then
begin
//申請緩沖
Buffer := AllocMem(CUANTOBUFFER + 1);
//創建STARTUPINFO
FillChar(Start,Sizeof(Start),#0);
start.cb := SizeOf(start);
start.hStdOutput := PaEscribir;
start.hStdInput := PaLeer;
start.dwFlags := STARTF_USESTDHANDLES +
STARTF_USESHOWWINDOW;
start.wShowWindow := SW_HIDE;
//執行子進程
if CreateProcess(nil,
PChar(Que),
@Seguridades,
@Seguridades,
true,
NORMAL_PRIORITY_CLASS,
nil,
nil,
start,
ProcessInfo)
then
begin
{Espera a que termine la ejecucion}
repeat
//使用信號量技術來避免CPU時間片被搶占
CuandoSale := WaitForSingleObject( ProcessInfo.hProcess,100);
Application.ProcessMessages;
until (CuandoSale <> WAIT_TIMEOUT);
{Leemos la Pipe}
repeat
BytesRead := 0;
{Llenamos un troncho de la pipe, igual a nuestro buffer}
//執行標準輸出
ReadFile(PaLeer,Buffer[0],CUANTOBUFFER,BytesRead,nil);
{La convertimos en una string terminada en cero}
Buffer[BytesRead]:= #0;
{Convertimos caracteres DOS a ANSI}
OemToAnsi(Buffer,Buffer);
EnMemo.Text := EnMemo.text + String(Buffer);
until (BytesRead < CUANTOBUFFER);
end;
FreeMem(Buffer);
//釋放資源
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(PaLeer);
CloseHandle(PaEscribir);
end;
end;