Windows GUI 程式開發需遵循嚴格規範:註冊視窗類別、建立視窗、進入訊息迴圈、處理視窗程序。本文以最精簡的「建立一個可顯示的視窗」為例,逐行解析 MASM32 組語實作,說明各 API 用途、結構體填寫細節、訊息迴圈運作機制,並提供可直接編譯執行的完整範本程式碼。
GetModuleHandleGetCommandLine (可選)WNDCLASSEX 結構並註冊視窗類別 RegisterClassExCreateWindowExShowWindowUpdateWindowGetMessage → TranslateMessage → DispatchMessageWM_DESTROY → PostQuitMessage 結束迴圈WNDCLASSEX STRUCT DWORD
cbSize DWORD ? ; 結構大小,必須填 SIZEOF WNDCLASSEX
style DWORD ? ; 類別樣式,如 CS_HREDRAW | CS_VREDRAW
lpfnWndProc DWORD ? ; 視窗程序位址 (OFFSET WndProc)
cbClsExtra DWORD ? ; 類別額外記憶體位元組數
cbWndExtra DWORD ? ; 視窗實例額外記憶體位元組數
hInstance DWORD ? ; 模組實例句柄
hIcon DWORD ? ; 大圖示句柄
hCursor DWORD ? ; 游標句柄
hbrBackground DWORD ? ; 背景筆刷句柄 (COLOR_WINDOW+1 等系統色)
lpszMenuName DWORD ? ; 選單資源名稱指標
lpszClassName DWORD ? ; 類別名稱指標 (ASCIIZ)
hIconSm DWORD ? ; 小圖示句柄
WNDCLASSEX ENDS
必填欄位:cbSize、lpfnWndProc、hInstance、lpszClassName、hbrBackground (否則背景不重繪)。
| 參數 | 範例值 | 說明 |
|---|---|---|
| dwExStyle | NULL | 擴充視窗樣式 (如 WS_EX_CLIENTEDGE 立體邊框) |
| lpClassName | ADDR ClassName | 已註冊類別名稱 |
| lpWindowName | ADDR AppName | 標題列文字 |
| dwStyle | WS_OVERLAPPEDWINDOW | 視窗樣式 (含標題列、系統選單、最大/最小/關閉鈕) |
| X, Y | CW_USEDEFAULT | 左上角座標,交由系統決定 |
| nWidth, nHeight | CW_USEDEFAULT | 寬高,交由系統決定 |
| hWndParent | NULL | 父視窗句柄,NULL 為最上層視窗 |
| hMenu | NULL | 選單句柄 (若類別已指定選單則忽略) |
| hInstance | hInst | 模組實例句柄 |
| lpParam | NULL | 建立參數 (WM_CREATE 的 lParam 傳遞) |
.WHILE TRUE
invoke GetMessage, ADDR msg, NULL, 0, 0
.BREAK .IF (!eax) ; WM_QUIT 時 eax=0 跳出
invoke TranslateMessage, ADDR msg ; 虛擬鍵碼 → 字元訊息 (WM_CHAR)
invoke DispatchMessage, ADDR msg ; 分派至視窗程序
.ENDW
GetMessage 會阻塞等待訊息,系統可調度其他執行緒/進程 (協同多工基礎)。TranslateMessage 將 WM_KEYDOWN/WM_KEYUP 轉換為 WM_CHAR,編輯框、文字輸入必須呼叫此函式,否則無法接收字元。DispatchMessage 依訊息中的 hwnd 找到視窗程序並呼叫。WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg == WM_DESTROY
invoke PostQuitMessage, NULL ; 發送 WM_QUIT 結束迴圈
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam ; 預設處理
ret
.ENDIF
xor eax, eax
ret
WndProc endp
WM_DESTROY,其他訊息交給 DefWindowProc (預設:繪製邊框、處理移動/縮放、系統選單等)。xor eax, eax 回傳 0,未處理訊息必須呼叫 DefWindowProc 並回傳其結果。.386
.model flat, stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke GetCommandLine
mov CommandLine, eax
invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
invoke ExitProcess, eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, 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_WINDOW+1
mov wc.lpszMenuName, NULL
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, NULL, ADDR ClassName, ADDR AppName, \
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, \
CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInst, NULL
mov hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd
.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
.IF uMsg == WM_DESTROY
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start
ml /c /coff /Cp /nologo template.asm
link /SUBSYSTEM:WINDOWS /LIBPATH:"\masm32\lib" template.obj /OUT:template.exe
| 現象 | 原因 | 修正 |
|---|---|---|
| 視窗不顯示 | 未呼叫 ShowWindow 或 UpdateWindow | 確認兩者皆在 CreateWindowEx 後呼叫 |
| 無法輸入文字 | 訊息迴圈缺少 TranslateMessage | 加入 TranslateMessage 於 GetMessage 之後 |
| 程式無法結束 | WM_DESTROY 未呼叫 PostQuitMessage | 在 WM_DESTROY 分支加入 PostQuitMessage |
| 連結錯誤 LNK2001 | 缺少 includelib user32.lib kernel32.lib | 於 .asm 開頭加入對應 includelib |
| 類別註冊失敗 | cbSize 未正確設定 | mov wc.cbSize, SIZEOF WNDCLASSEX |
掌握上述流程與範本,即擁有了所有 Win32 組語 GUI 專案的起點。後續功能 (選單、控件、對話方塊、繪圖、檔案 I/O) 皆在這個框架上擴充。建議將此範本存為 skeleton.asm,新專案直接複製修改。