?? 021.txt
字號:
第二十一課 管道
--------------------------------------------------------------------------------
這一講將探索一下管道,看看它是什么、有什么用。為使之更加生動有趣,我將用怎樣改變 Edit 控件的背景色和文本顏色來說明此技術。
理論:
管道,顧名思義就是有兩個端的通道。可以使用管道在進程間、同一進程內進行數據交換,就像手提式無線電話機一樣。把管道的一端給另一方,他就可以借助管道和你通訊了。
有兩種管道,即有名管道和匿名管道。匿名管道就是沒有名字的管道了,也就是說在使用它們時不需要知道其名字。而有名管道正好相反,在使用前必須知道其名字。
也可以根據管道的特性來分類,即是單向的還是雙向的。單向管道,數據只能沿一個方向移動,從一端流向另一端,而雙向管道數據可以在兩端間自由交換。匿名管道通常是單向的而有名管道通常是雙向的。有名管道常用于一個服務器聯絡多個客戶端的網絡環境。
這一講將詳細討論一下匿名管道。匿名管道主要目的是作為父進程與子進程、子進程之間通訊的聯結通路。在處理控制臺問題時,匿名管道是相當有用的。控制臺應用程序就是使用控制臺作為輸入和輸出的一種 Win32 應用程序。一個控制臺就像一個 DOS 窗口。但控制臺應用程序的的確確是32位的應用程序,它可以向其它圖形程序一樣使用 GUI 函數,只不過它碰巧使用了控制臺罷了。
控制臺應用程序有三個用于輸入輸出的標準句柄,它們是標準輸入、標準輸出和標準錯誤句柄。標準輸入用于從控制臺讀或取信息而標準輸出用于往控制臺寫或打印信息。標準錯誤用于匯報輸出不能重定向的錯誤。
控制臺應用程序可以通過調用函數 GetStdHandle 來獲得這三個句柄。一個圖形應用程序沒有控制臺,如果在其中調用GetStdHandle 就會返回錯誤;如果的確要使用控制臺,可以調用AllocConsole 來分配一個新的控制臺以使用,但當處理完成時,別忘了調用 FreeConsole 來釋放控制臺。
匿名管道用得最多的功能就是 重定向子進程的標準輸入和標準輸出。父進程可以是一個控制臺或者是圖形程序,而子進程必須是控制臺應用程序。眾所周知,控制臺應用使用標準輸入輸出句柄。若要重定向輸入輸出,就得用指向管道一端的句柄來替換這個標準句柄。控制臺應用程序不會知道我們使用了指向管道任一端的句柄,它會把這個句柄作為標準句柄來看待。借用面向對象的術語,這就是多態性的一種。因為子進程不需作任何改動,因此這種方法是非常有用的。
關于控制臺應用程序應該掌握的另一點就是它從哪獲得標準句柄。當一個控制臺應用程序被創建時,父進程有兩種選擇:為子進程創建一個新的控制臺或者是讓子進程繼承自己的控制臺。若使用后者,那父進程本身必須是一個控制臺應用程序,或者如果是 GUI 應用程序,它必須首先調用 AllocConsole 分配了一個控制臺。
通過調用 CreatePipe 來創建一個匿名管道,它的原型為:
CreatePipe proto pReadHandle:DWORD, \
pWriteHandle:DWORD,\
pPipeAttributes:DWORD,\
nBufferSize:DWORD
pReadHandle 雙字指針變量,指向管道讀端的句柄。
pWriteHandle 雙字指針變量,指向管道寫端的句柄
pPipeAttributes 雙字指針變量,指向SECURITY_ATTRIBUTES 結構,其用于決定讀寫句柄是否可以被子進程繼承
nBufferSize 建議管道留給用戶使用的緩沖區的大小,這僅僅是個建議值,可以用 NULL 來使用缺省值
如果函數調用成功返回值為非零,否則為零。成功調用之后,就會得到兩個句柄,一個指向管道的讀出端,另一個指向管道的寫入端。現在我將要把重點放到重定向子控制臺程序的標準輸出到自己進程的所需的步驟上。注意我的這個方法不同于Borland 公司的 API 參考上的例子。Win32 API 參考上假設父進程是控制臺應用程序,因此子進程可以繼承它的標準句柄。然而大多數情況下我們需要重定向控制臺應用程序的輸出到 GUI 應用程序。
創建匿名管道使用 CreatePipe ,同時別忘了把 SECURITY_ATTRIBUTES 結構成員bInheritable 設置為TRUE,這樣才可以繼承句柄。
現在要準備好創建進程的函數即CreateProcess 的參數,只有它才可以裝載子控制臺應用程序。STARTUPINFO 是一個重要的結構,它決定了子進程出現時主窗口的外觀,它對于我們的目標也是至關重要的。通過這個結構就可以隱藏主窗口并且把管道句柄傳遞給子進程。
以下就是必須要填寫的成員:
cb STARTUPINFO結構的大小
dwFlags 二進制標志位,它決定本結構的哪些成員有效,也決定主窗口是顯示還是隱藏的狀態。在我們的程序中使用STARTF_USESHOWWINDOW 和 STARTF_USESTDHANDLES的組合
hStdOutput 和hStdError 你想要子進程使用的標準輸出和標準錯誤句柄,對我們來說,我們將把管道的寫端作為子進程的標準輸出和錯誤。因此當子進程往標準輸出或標準錯誤發送信息時,它實際上把這些信息通過管道傳給了父進程
wShowWindow 決定主窗口是顯示還是隱藏。我們不希望顯示子進程的主窗口,因此把該成員置成SW_HIDE
調用CreateProcess 來創建子進程,但調用成功后子進程仍然不處于激活狀態。它被裝進了內存但并沒有立即運行。
在父進程中關閉管道的寫端也是必須的。這是因為父進程并不使用管道的寫句柄,而且如果一個管道有兩個寫入端也就不會工作,因此我們在從管道往外讀數據之前必須關閉管道的寫端。但是不能在調用CreateProcess 之前關閉,否則管道就壞了。你應當在CreateProcess 剛剛返回并且在讀數據之前關閉管道的寫端。
現在就可以通過函數ReadFile 在管道的讀端讀數據了。通過使用ReadFile ,可以使子進程處于運行狀態。它將開始執行,并且當它往標準輸出( 實際上是管道的寫端 )上寫數據時,數據就會被送至管道的讀端。應當不停調用ReadFile 直至它的返回值為 0 ,也就是說再也沒有數據可讀了。對從管道讀來的數據你可以進行任何處理,在我們的例子中它被顯示在 Edit 控件中。
記得用完后關閉管道的讀句柄。
代碼舉例:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101 ; the ID of the main menu
IDM_ASSEMBLE equ 40001
.data
ClassName db "PipeWinClass",0
AppName db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError db "Error during pipe creation",0
CreateProcessError db "Error during process creation",0
CommandLine db "ml /c /coff /Cp test.asm",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX
mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_APPWORKSPACE
mov wc.lpszMenuName,IDR_MAINMENU
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
mov hwnd,eax
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL rect:RECT
LOCAL hRead:DWORD
LOCAL hWrite:DWORD
LOCAL startupinfo:STARTUPINFO
LOCAL pinfo:PROCESS_INFORMATION
LOCAL buffer[1024]:byte
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -