管道是進程間通訊的經典機制,Win32 匿名管道專為父子進程單向資料流設計。本文實作一個 GUI 程式,建立匿名管道、啟動 MASM 組譯器 (ml.exe) 作為子進程,將其標準輸出/錯誤重導向至管道,父進程讀取管道資料並即時顯示於編輯控制項,並示範如何自訂編輯控制項的文字/背景顏色。
SECURITY_ATTRIBUTES.bInheritHandle = TRUE 讓子進程繼承管道控制代碼。| API | 用途 |
|---|---|
CreatePipe | 建立管道,返回讀/寫控制代碼 |
CreateProcess | 啟動子進程,透過 STARTUPINFO 傳遞標準控制代碼 |
ReadFile / WriteFile | 管道讀寫(同檔案 API) |
GetStartupInfo / STARTUPINFO | 設定子進程主視窗顯示狀態、標準輸入/輸出/錯誤控制代碼 |
CreatePipe(&hRead, &hWrite, &sa, 0),sa.bInheritHandle = TRUE。cb = sizeof(STARTUPINFO)dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLESwShowWindow = SW_HIDE(隱藏子進程主視窗)hStdOutput = hStdError = hWrite(子進程寫入標準輸出/錯誤實為寫入管道)CreateProcess(NULL, "ml /c /coff /Cp test.asm", ..., TRUE, ..., &si, &pi),bInheritHandles = TRUE。CloseHandle(hWrite)(必須在 CreateProcess 之後、ReadFile 之前,否則管道有兩個寫端會導致讀取端永遠不會遇到 EOF)。ReadFile(hRead, buffer, sizeof(buffer)-1, &bytesRead, NULL) 直到返回 0(EOF)。EM_SETSEL -1, 0 移動游標到末尾EM_REPLACESEL FALSE, buffer 插入文字CloseHandle(hRead)、CloseHandle(pi.hProcess)、CloseHandle(pi.hThread)。編輯控制項重繪前會向父視窗發送 WM_CTLCOLOREDIT,wParam 為其 DC 控制代碼。處理方式:
.elseif uMsg == WM_CTLCOLOREDIT
invoke SetTextColor, wParam, Yellow ; 文字顏色
invoke SetBkColor, wParam, Black ; 背景顏色
invoke GetStockObject, BLACK_BRUSH ; 返回刷子控制代碼供系統填充背景
ret
必須返回刷子控制代碼,否則背景不會更新。
.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
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.nLength, 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
; 設定 STARTUPINFO
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
CreateProcess 成功返回後、第一次 ReadFile 之前。過早關閉會導致子進程繼承失敗;不關閉會導致 ReadFile 永不返回 EOF(因為父進程仍持有寫端)。EM_SETSEL -1, 0 移至末尾,EM_REPLACESEL 插入,避免 SetWindowText 覆蓋舊內容。Overlapped I/O 或執行緒避免 GUI 凍結。匿名管道配合 STARTUPINFO 重導向標準輸出,是將主控台程式整合進 GUI 應用程式的標準技術。掌握管道建立、控制代碼繼承、父進程關閉寫端時機、非同步讀取與 GUI 更新同步,即可打造出功能強大的建構系統、除錯器前端、自動化測試工具等應用。