程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 匯編教程:管道

匯編教程:管道

編輯:匯編語言

這一講將探索一下管道,看看它是什麼、有什麼用。為使之更加生動有趣,我將用怎樣改變 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
LOCAL bytesRead:DWORD
LOCAL hdc:DWORD
LOCAL sat:SECURITY_ATTRIBUTES
.if uMsg==WM_CREATE
invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
mov hwndEdit,eax
.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetBkColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret
.elseif uMsg==WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
.elseif uMsg==WM_COMMAND
.if lParam==0
mov eax,wParam
.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE
invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
.if eax==NULL
invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
.else
mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE
invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
.if eax==NULL
invoke MessageBox,hWnd,addr CreateProcessError,addr AppName,MB_ICONERROR+MB_OK
.else
invoke CloseHandle,hWrite
.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw
.endif
invoke CloseHandle,hRead
.endif
.endif
.endif
.elseif uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
.endif
xor eax,eax
ret
WndProc endp
end start

分析:

這個例子調用 ml.exe 來匯編一個名為 test.asm 的程序,並且重定向 ml.exe 的輸出到客戶區的 Edit 控件中。當程序被加載時,象往常一樣要注冊窗口類和創建主窗口。在主窗口被創建的過程中要做的第一件事就是創建用於顯示程序 ml.exe 輸出的 Edit 控件。

現在有趣的事來了,我們將改變此 Edit 控件的文本顏色和背景色。當 Edit 控件將要重畫客戶區時,它會給父窗口發送 WM_CTLCOLOREDIT 消息。參數 wParam 包含了用於畫控件自己的客戶區設備描述符的句柄 (HDC) 。我們可以利用這種機制來修改 HDC 的特性。


.elseif uMsg==WM_CTLCOLOREDIT
invoke SetTextColor,wParam,Yellow
invoke SetTextColor,wParam,Black
invoke GetStockObject,BLACK_BRUSH
ret

SetTextColor 把文本顏色變為黃色,背景顏色變為黑色。最後我們返回一個通過調用GetStockObject 而得到黑色刷子的句柄。處理WM_CTLCOLOREDIT 必須返回一個刷子的句柄,因為 Windows 將要使用這個刷子來重畫 Edit 控件的背景。在這個例子中,我希望背景是黑色,所以返回了一個黑色刷子的句柄。

現在當用戶選擇 Assemble 子菜單時,就會創建一個匿名管道。


.if ax==IDM_ASSEMBLE
mov sat.niLength,sizeof SECURITY_ATTRIBUTES
mov sat.lpSecurityDescriptor,NULL
mov sat.bInheritHandle,TRUE

在調用CreatePipe 之前,必須要填寫SECURITY_ATTRIBUTES 結構。如果我們不關心安全性的話,可以在lpSecurityDescriptor 成員中填入 NULL 。bInheritHandle 則必須為 TRUE ,這樣管道的句柄才可以被子進程繼承。本文來自編程入門網

invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL

在此之後,我們調用CreatePipe 來創建管道,如果成功,那麼變量hRead 和 hWrite 將分別被填入相應的管道的讀出端和寫入端的句柄。


mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax, hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

下一步就是填寫STARTUPINFO 結構了。調用 GetStartupinfo 用父進程的缺省值來填寫STARTUPINFO 結構。如果要使程序同時工作在 Windows9x 和 Windows NT 下,就必須調用GetStartupInfo 來填寫STARTUPINFO 結構。調用返回後,就可以修改重要的成員了。因為我們要子進程輸出到父進程而不是缺省的標准輸出和標准錯誤,所以我們把hStdOutput 和 hStdError 都賦成管道寫端的句柄。為了隱藏子進程的主窗口,必須把成員變量wShowWidow 賦值為SW_HIDE 。最後通過把成員 dwFlags 賦值為STARTF_USESHOWWINDOW 和 STARTF_USESTDHANDLES 來指明成員hStdOutput, hStdError 和 wShowWindow 是有效的。

invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo

現在調用CreateProcess 來創建子進程。注意為使管道工作,參數bInheritHandles 必須設置為TRUE。 invoke CloseHandle,hWrite 成功創建子進程之後,在父進程中必須關閉管道的寫端。我們已經把寫端的句柄通過結構STARTUPINFO 傳給了子進程。如果不關閉,那麼管道就有兩個寫入端,而這樣的管道是不會工作的。所以必須在創建子進程後但在讀數據前關閉這個句柄。


.while TRUE
invoke RtlZeroMemory,addr buffer,1024
invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
.if eax==NULL
.break
.endif
invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
.endw

現在已經准備好從子進程的標准輸出讀數據了。直到再也沒有數據了,即 ReadFile 返回為 NULL時才會退出循環,否則一直會等待數據。我們調用ReadFile 之前先調用RtlZeroMemory 來清空內存,並且用管道的讀句柄代替文件句柄。注意讀數據的最大長度為 1023 ( sizeof(buffer)-1 ),因為我們需要把接受的字符變為一個 Edit 控件可以處理的 ASCII 串。當ReadFile 返回時,就把此數據傳給 Edit 控件。然而這有一個小小的問題,如果使用SetWindowText API 往 Edit 控件中寫數據,新數據就會覆蓋已存在的舊數據,而我們想把新數據添加在已有數據的後面。為達此目的,首先通過發送一個 wParam 為-1的 EM_SETSEL 消息,把 Edit 控件的輸入焦點移動到文本的末端;然後發送一個 EM_REPLACESEL 消息把數據添加後面。

invoke CloseHandle,hRead

當ReadFile 返回為NULL時,就跳出循環並關閉管道的讀句柄。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved