首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

Win32 組合語言匿名管道實作:重導向子程式輸出到編輯控制項

     編輯:組合語言

前言

管道是進程間通訊的經典機制,Win32 匿名管道專為父子進程單向資料流設計。本文實作一個 GUI 程式,建立匿名管道、啟動 MASM 組譯器 (ml.exe) 作為子進程,將其標準輸出/錯誤重導向至管道,父進程讀取管道資料並即時顯示於編輯控制項,並示範如何自訂編輯控制項的文字/背景顏色。

匿名管道核心概念

  • 單向:一端寫入,一端讀取。
  • 無名稱:僅存在於建立它的父進程及其子進程間,透過控制代碼傳遞。
  • 繼承性:SECURITY_ATTRIBUTES.bInheritHandle = TRUE 讓子進程繼承管道控制代碼。

關鍵 API

API用途
CreatePipe建立管道,返回讀/寫控制代碼
CreateProcess啟動子進程,透過 STARTUPINFO 傳遞標準控制代碼
ReadFile / WriteFile管道讀寫(同檔案 API)
GetStartupInfo / STARTUPINFO設定子進程主視窗顯示狀態、標準輸入/輸出/錯誤控制代碼

重導向標準輸出的完整步驟

  1. 建立管道:CreatePipe(&hRead, &hWrite, &sa, 0),sa.bInheritHandle = TRUE。
  2. 準備 STARTUPINFO:
    • cb = sizeof(STARTUPINFO)
    • dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES
    • wShowWindow = SW_HIDE(隱藏子進程主視窗)
    • hStdOutput = hStdError = hWrite(子進程寫入標準輸出/錯誤實為寫入管道)
  3. 啟動子進程:CreateProcess(NULL, "ml /c /coff /Cp test.asm", ..., TRUE, ..., &si, &pi),bInheritHandles = TRUE。
  4. 父進程關閉寫端:CloseHandle(hWrite)(必須在 CreateProcess 之後、ReadFile 之前,否則管道有兩個寫端會導致讀取端永遠不會遇到 EOF)。
  5. 迴圈讀取管道:ReadFile(hRead, buffer, sizeof(buffer)-1, &bytesRead, NULL) 直到返回 0(EOF)。
  6. 將資料追加到編輯控制項:
    • EM_SETSEL -1, 0 移動游標到末尾
    • EM_REPLACESEL FALSE, buffer 插入文字
  7. 清理:CloseHandle(hRead)、CloseHandle(pi.hProcess)、CloseHandle(pi.hThread)。

自訂編輯控制項顏色:WM_CTLCOLOREDIT

編輯控制項重繪前會向父視窗發送 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

關鍵細節與常見錯誤

  1. SECURITY_ATTRIBUTES.bInheritHandle = TRUE:否則子進程無法繼承管道控制代碼,重導向失效。
  2. GetStartupInfo 填充預設值:確保在 Win9x/NT 都正常運作,再修改特定欄位。
  3. 關閉父進程寫端的時機:必須在 CreateProcess 成功返回後、第一次 ReadFile 之前。過早關閉會導致子進程繼承失敗;不關閉會導致 ReadFile 永不返回 EOF(因為父進程仍持有寫端)。
  4. ReadFile 緩衝區大小:留 1 位元組給字串結束符,避免緩衝區溢位。
  5. 編輯控制項追加文字:EM_SETSEL -1, 0 移至末尾,EM_REPLACESEL 插入,避免 SetWindowText 覆蓋舊內容。
  6. WM_CTLCOLOREDIT 必須返回刷子:系統使用該刷子填充背景,否則顏色不生效。

延伸應用

  • 雙向通訊:建立兩個管道(父讀/子寫、父寫/子讀),實作互動式命令列工具封裝。
  • 有名管道:跨網路、非親緣進程通訊,支援多客戶端伺服器模型。
  • 非同步讀取:使用 Overlapped I/O 或執行緒避免 GUI 凍結。
  • 重導向標準輸入:父進程寫入管道寫端,子進程從標準輸入讀取,實現自動化輸入。

總結

匿名管道配合 STARTUPINFO 重導向標準輸出,是將主控台程式整合進 GUI 應用程式的標準技術。掌握管道建立、控制代碼繼承、父進程關閉寫端時機、非同步讀取與 GUI 更新同步,即可打造出功能強大的建構系統、除錯器前端、自動化測試工具等應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved