Win32彙編教程十二 管道操作 (轉)

gugu99發表於2007-12-29
Win32彙編教程十二 管道操作 (轉)[@more@]


--------------------------------------------------------------------------------

在這兒本節的所有源

概述

引入了多程式和多執行緒機制。同時也提供了多個程式之間的通訊手段,包括剪貼簿、DDE、OLE、管道等,和其他通訊手段相比,管道有它自己的限制和特點,管道實際上是一段共享區,程式把共享訊息放在那裡。並透過一些 提供資訊。
管道是兩個頭的東西,每個頭各連線一個程式或者同一個程式的不同程式碼,按照管道的類別分有兩種管道,匿名的和命名的;按照管道的傳輸方向分也可以分成兩種,單向的雙向的。根據管道的特點,命名管道通常用在環境下不同上執行的程式之間的通訊(當然也可以用在同一臺機的不同程式中)它可以是單向或雙向的;而匿名管道只能用在同一臺計算機中,它只能是單向的。匿名管道其實是透過用給了一個指定名字的有名管道來實現的。
使用管道的好處在於:讀寫它使用的是對操作的 api,結果操作管道就和操作檔案一樣。即使你在不同的計算機之間用命名管道來通訊,你也不必瞭解和自己去實現網路間通訊的具體細節。

我們簡單的介紹一下命名管道的使用。

命名管道是由端的程式建立的,管道的命名必須遵循特定的命名方法,就是 ".pipe管道名",當作為客戶端的程式要使用時,使用"計算機名pipe管道名" 來開啟使用,具體步驟如下:

服務端透過 CreateNamedPipe 建立一個命名管道的例項並返回用於今後操作的控制程式碼,或為已存在的管道建立新的例項。
服務端偵聽來自客戶端的連線請求,該功能透過 ConnectNamedPipe 函式實現。
客戶端透過函式 WaitNamedPipe 來等待管道的出現,如果在超時值變為零以前,有一個管道可以使用,則 WaitNamedPipe 將返回 True,並透過 CreateFile 或 CallNamedPipe 來呼叫對服務端的連線。
此時服務端將接受客戶端的連線請求,成功建立連線,服務端 ConnectNamedPipe 返回 True
建立連線之後,客戶端與伺服器端即可透過 ReadFile 和 WriteFile,利用得到的管道檔案控制程式碼,彼此間進行資訊交換。
當客戶端與服務端的通訊結束,客戶端呼叫 CloseFile,服務端接著呼叫 DinnectNamedPipe。最後呼叫函式CloseHandle來關閉該管道。
由於命名管道使用時作為客戶端的程式必須知道管道的名稱,所以更多的用在同一“作者”編寫的伺服器/工作站程式中,你不可能隨便找出一個程式來要求它和你寫的程式來透過命名管道通訊。而匿名管道的使用則完全不同,它允許你和完全不相干的程式通訊,條件是這個程式透過控制檯“console”來輸入輸出,典型的例子是老的 D應用程式,它們在執行時 Windows 為它們開了個 Dos 視窗,它們的輸入輸出就是 console 方式的。還有一些標準的 程式也使用控制檯輸入輸出,如果在 Win32 中不想使用圖形介面,你照樣可以使用 AllocConsole 得到一個控制檯,然後透過 GetStdHandle 得到輸入或輸出控制程式碼,再透過 WriteConsole 或 WriteFile 把結果輸出到控制檯(通常是一個象 Dos 視窗)的螢幕上。雖然這些程式看起來象 Dos 程式,但它們是不折不扣的 Win32 程式,如果你在純 Dos 下使用,就會顯示“The program must run under Windows!”。

一個控制檯有三個控制程式碼:標準輸入、標準輸出和和標準錯誤控制程式碼,標準輸入、標準輸出控制程式碼是可以重新定向的,你可以用匿名管道來代替它,這樣一來,你可以在管道的另一端用別的程式來接收或輸入,而控制檯一方並沒有感到什麼不同,就象 Dos 下的 > 或者 < 可以重新定向輸出或輸入一樣。通常控制檯程式的輸入輸出如下:

(控制檯程式output) write ----&gt 標準輸出裝置(一般是螢幕)
(控制檯程式input) read
而用管道代替後:

(作為子程式的控制檯程式output) write ----&gt 管道1 ----&gt read (父程式)
(作為子程式的控制檯程式input) read
使用匿名管道的步驟如下:

使用 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
D 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_H,這樣子程式執行時不顯示視窗。
填寫好以後,就可以用 CreateProcess 來執行子程式了,具體有關執行子程式的操作可以參考上一篇教程《程式控制》

3. 在程式中可以用 PeekNamedPipe 查詢子程式有沒有輸出,原形如下:

BOOL 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 這四個控制程式碼。

下面給出了一個例子程式,這個程式是上篇教程《程式控制》的例子的擴充,如果你對有的 api 感到陌生的話,請先閱讀上一篇教程。

源程式 - 原始檔

DE equ 0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Programmed by 羅雲彬, bigluo@telekbird.com.cn
; site:
; LuoYunBin's Win32 ASM page (羅雲彬的程式設計樂園)
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 版本資訊
; 彙編教程附帶例子程式 - 管道例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.386
.model flat, stdcall
option casemap :none ; case sensitive
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 資料
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
include gdi32.inc

includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
includelib gdi32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 資料
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
MENU_MAIN equ 2000
IDM_EXEC equ 2001
IDM_EXIT equ 2002

F_RUNNING equ 0001h ;程式在執行中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 資料段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?

stStartUp STARTUPINFO >

hInstance dd ?
hMenu dd ?
hWinMain dd ?
hWinText dd ?
hFont dd ?
hRunThread dd ?
hRead1 dd ?
hWrite1 dd ?
hRead2 dd ?
hWrite2 dd ?
szBuffer 512 dup (?)

dwFlag dd ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data

szMenuExecute db '連線 MS-&DOS 方式',0
szExcuteError db '啟動應用程式錯誤!',0
szCaption db '管道示例程式 ... .net',0
szClassName db 'PipeExample',0
;szDllName db 'riched32.dll',0
;szClassNameRedit db 'RichEdit',0
szDllName db 'riched20.dll',0
szClassNameRedit db 'richedit20a',0
szCommand db 'c:command.com',0

stLogFont LOGFONT <24,0,0,0,FW_NORMAL,
0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_STROKE_PRECIS,DEFAULT_QUALITY,
DEFAULT_PITCH or FF_SWISS,"Fixedsys">

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 程式碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code

if DEBUG
include Debug.asm
endif
include Win.asm

;********************************************************************
; 執行程式用的執行緒
; 1. 用 CreateProcess 建立程式
; 2. 用 WaitForSingleOject 等待程式結束
;********************************************************************
_RunThread proc uses ebx ecx edx esi edi,
dwParam:DWORD
local @stSecurity:SECURITY_ATTRIBUTES
local @dwExitCode
local @dwBytesRead
local @stRange:CHARRANGE

or dwFlag,F_RUNNING
;********************************************************************
; “執行”選單改為“結束”
;********************************************************************
invoke EnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED
;********************************************************************
; 建立管道
;********************************************************************
mov @stSecurity.nLength,sizeof SECURITY_ATTRIBUTES
mov @stSecurity.lpSecurityDescriptor,NULL
mov @stSecurity.bInheritHandle,TRUE
invoke CreatePipe,addr hRead1,addr hWrite1,addr @stSecurity,NULL
invoke CreatePipe,addr hRead2,addr hWrite2,addr @stSecurity,NULL

;********************************************************************
; 執行檔案,如果成功則等待程式結束
;********************************************************************
invoke GetStartupInfo,addr stStartUp
mov eax,hRead1
mov stStartUp.hStdInput,eax
mov eax,hWrite2
mov stStartUp.hStdOutput,eax
mov stStartUp.hStdError,eax
mov stStartUp.dwFlags,STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW
mov stStartUp.wShowWindow,SW_HIDE
invoke CreateProcess,NULL,addr szCommand,NULL,NULL,
NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo
.if eax != 0
.while TRUE
invoke GetExitCodeProcess,stProcInfo.hProcess,addr @dwExitCode
.break .if @dwExitCode != STILL_ACTIVE
invoke PeekNamedPipe,hRead2,addr szBuffer,511,addr @dwBytesRead,NULL,NULL
.if @dwBytesRead != 0
invoke RtlZeroMemory,addr szBuffer,512
invoke ReadFile,hRead2,addr szBuffer,@dwBytesRead,addr @dwBytesRead,NULL
mov @stRange.cpMin,-1
mov @stRange.cpMax,-1
invoke SendMessage,hWinText,EM_EXSETSEL,0,addr @stRange
invoke SendMessage,hWinText,EM_REPLACESEL,FALSE,addr szBuffer
invoke SendMessage,hWinText,EM_SCROLLCARET,NULL,NULL
invoke SendMessage,hWinText,WM_SETFONT,hFont,0
.endif
.endw
invoke CloseHandle,stProcInfo.hProcess
invoke CloseHandle,stProcInfo.hThread
.else
invoke MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR
.endif
;********************************************************************
; 關閉管道
;********************************************************************
invoke CloseHandle,hRead1
invoke CloseHandle,hWrite1
invoke CloseHandle,hRead2
invoke CloseHandle,hWrite2
;********************************************************************
; 把“結束”選單改為“執行”
;********************************************************************
invoke EnableMenuItem,hMenu,IDM_EXEC,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_EXIT,MF_ENABLED
invoke EnableWindow,hWinText,FALSE
and dwFlag,not F_RUNNING
ret

_RunThread endp

;********************************************************************
; 視窗程式
;********************************************************************
WndMainProc proc uses ebx edi esi,
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

mov eax,wMsg
;********************************************************************
.if eax == WM_CREATE
mov eax,hWnd
mov hWinMain,eax
call _Init
;********************************************************************
.elseif eax == WM_SIZE
mov edx,lParam
mov ecx,edx
shr ecx,16
and edx,0ffffh
invoke MoveWindow,hWinText,0,0,edx,ecx,TRUE
invoke PostMessage,hWinText,WM_SIZE,wParam,lParam
;********************************************************************
.elseif eax == WM_CLOSE
test dwFlag,F_RUNNING
.if ZERO?
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
.endif
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDM_EXEC
;********************************************************************
; 如果沒有在執行中(dwFlag 沒有置位) 則建立執行緒,線上程中執行程式
; 如果已經在執行中,則用 TenateProcess 終止執行
;********************************************************************
test dwFlag,F_RUNNING
.if ZERO?
invoke EnableWindow,hWinText,TRUE
invoke SetFocus,hWinText
invoke CreateThread,NULL,NULL,offset _RunThread,
NULL,NULL,offset hRunThread
.else
invoke TerminateProcess,stProcInfo.hProcess,-1
.endif
.elseif ax == IDM_EXIT
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
.endif
.else
invoke DefWindowProc,hWnd,wMsg,wParam,lParam
ret
.endif
xor eax,eax
ret

WndMainProc endp
;********************************************************************
; 程式入口
;********************************************************************
start:
call _WinMain
invoke ExitProcess,NULL
;********************************************************************
_WinMain proc
local @stWcMain:WNDCLASSEX
local @stMsg:MSG
local @hRichEdit

invoke LoadLibrary,offset szDllName
mov @hRichEdit,eax

invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadMenu,hInstance,MENU_MAIN
mov hMenu,eax
;***************** 註冊視窗類 ***************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWcMain.hCursor,eax
mov @stWcMain.cbSize,sizeof WNDCLASSEX
mov @stWcMain.hIconSm,0
mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW
mov @stWcMain.lpfnWndProc,offset WndMainProc
mov @stWcMain.cbClsExtra,0
mov @stWcMain.cbWndExtra,0
mov eax,hInstance
mov @stWcMain.hInstance,eax
invoke LoadIcon,hInstance,ICO_MAIN
mov @stWcMain.hIcon,eax
mov @stWcMain.hbrBackground,COLOR_BTNFACE+1
mov @stWcMain.lpszClassName,offset szClassName
mov @stWcMain.lpszMenuName,0
invoke RegisterClassEx,addr @stWcMain
;***************** 建立輸出視窗 *****************************************
invoke CreateWindowEx,NULL,
offset szClassName,offset szCaption,
WS_OVERLAPPEDWINDOW,
0,0,680,420,
NULL,hMenu,hInstance,NULL

invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;********************************************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
invoke FreeLibrary,@hRichEdit
invoke Delete,hFont
ret

_WinMain endp

;********************************************************************
; 輸入程式
;********************************************************************
_InputProc proc uses ebx edi esi,
hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
local @szBuffer[4]:BYTE
local @dwBytesWrite

mov eax,uMsg
.if eax == WM_CHAR
mov eax,wParam
movzx eax,al
mov dword ptr @szBuffer,eax
test dwFlag,F_RUNNING
.if !ZERO?
invoke WriteFile,hWrite1,addr @szBuffer,1,addr @dwBytesWrite,NULL
.endif
xor eax,eax
ret
.endif
invoke GetWindowLong,hWnd,GWL_USERDATA
invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam
ret

_InputProc endp
;********************************************************************
_Init proc

;*************** 建立輸出 RICHEDIT 視窗 ***********************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassNameRedit,
NULL,WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLL
OR ES_MULTILINE OR ES_AUTOHSCROLL OR ES_AUTOVSCROLL,
0,0,0,0,
hWinMain,NULL,hInstance,NULL
mov hWinText,eax
;*************** 設定字型 ***********************************************
invoke CreateFontIndirect,offset stLogFont
mov hFont,eax
invoke SendMessage,hWinText,WM_SETFONT,hFont,0
invoke SendMessage,hWinText,EM_SETREADONLY,TRUE,NULL

invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc
invoke SetWindowLong,hWinText,GWL_USERDATA,eax
invoke EnableWindow,hWinText,FALSE

invoke _CenterWindow,hWinMain
invoke SetFocus,hWinText

ret

_Init endp
;********************************************************************
end start

程式的分析和要點

在程式中,我先建立了一個 Richedit 用來顯示子程式的輸出,同時將 RichEdit 子類化,擷取它的鍵盤輸入以便把它發給子程式

invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc

這條語句將 RichEdit 的過程指到了 _InputProc 中,然後在 _InputProc 的 WM_CHAR 中將鍵入的字元 WriteFile 到管道中,我在程式中先建立了兩個管道,然後執行 c:command.com,這樣就得到了一個 dos 的命令列程式,然後在迴圈中透過 PeekNamedPipe 檢測子程式有無輸出,如果有的話則透過 ReadFile 讀出,在顯示到 RichEdit 中。

在執行例子程式的時候要注意,你可以在這個“Command.com” 中執行幾乎所有的別的程式,但是不要執行如 ucdos,pctools 之類不使用標準輸入輸出的程式(就是在 dos 下用不了“>”或者“
在這裡還可以引申出匿名管道的另一個用法,如果你執行的不是 command.com 而是類似於 arj.exe 的程式,然後也不用把它的輸出顯示到 RichEdit 中,而是在程式中處理,那麼,你就可以編寫一個 winarj,當然你只需編寫視窗介面和 arj.exe 之間的配合而已。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-996034/,如需轉載,請註明出處,否則將追究法律責任。

相關文章