理論:
Windows 程序中,在寫圖形用戶界面時需要調用大量的標准 Windows Gui 函數。其實這對用戶和程序員來說都有好處,對於用戶,面對的是同一套標准的窗口,對這些窗口的操作都是一樣的,所以使用不同的應用程序時無須重新學習操作。對程序員來說,這些 Gui 源代碼都是經過了微軟的嚴格測試,隨時拿來就可以用的。當然至於具體地寫程序對於程序員來說還是有難度的。為了創建基於窗口的應用程序,必須嚴格遵守規范。作到這一點並不難,只要用模塊化或面向對象的編程方法即可。
下面我就列出在桌面顯示一個窗口的幾個步驟:
得到您應用程序的句柄(必需);
得到命令行參數(如果您想從命令行得到參數,可選);
注冊窗口類(必需,除非您使用 Windows 預定義的窗口類,如 MessageBox 或 dialog box;
產生窗口(必需);
在桌面顯示窗口(必需,除非您不想立即顯示它);
刷新窗口客戶區;
進入無限的獲取窗口消息的循環;
如果有消息到達,由負責該窗口的窗口回調函數處理;
如果用戶關閉窗口,進行退出處理。
相對於單用戶的 DOS 下的編程來說,Windows 下的程序框架結構是相當復雜的。但是 Windows 和 DOS 在系統架構上是截然不同的。Windows 是一個多任務的操作系統,故系統中同時有多個應用程序彼此協同運行。這就要求 Windows 程序員必須嚴格遵守編程規范,並養成良好的編程風格。
內容:
下面是我們簡單的窗口程序的源代碼。在進入復雜的細節前,我將提綱挈領地指出幾點要點:
您應當把程序中要用到的所有常量和結構體的聲明放到一個頭文件中,並且在源程序的開始處包含這個頭文件。這麼做將會節省您大量的時間,也免得一次又一次的敲鍵盤。目前,最完善的頭文件是 hutch 寫的,您可以到 hutch 或我的網站下載。您也可以定義您自己的常量和結構體,但最好把它們放到獨立的頭文件中
用 includelib 指令,包含您的程序要引用的庫文件,譬如:若您的程序要調用 "MessageBox", 您就應當在源文件中加入如下一行: includelib user32.lib 這條語句告訴 MASM 您的程序將要用到一些引入庫。如果您不止引用一個庫,只要簡單地加入 includelib 語句,不要擔心鏈接器如何處理這麼多的庫,只要在鏈接時用鏈接開關 /LIBPATH 指明庫所在的路徑即可。
在其它地方運用頭文件中定義函數原型,常數和結構體時,要嚴格保持和頭文件中的定義一致,包括大小寫。在查詢函數定義時,這將節約您大量的時間;
在編譯,鏈接時用makefile文件,免去重復敲鍵。
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
.DATA ; initialized data
ClassName db "SimpleWinClass",0 ; the name of our window class
AppName db "Our First Window",0 ; the name of our window
.DATA? ; Uninitialized data
hInstance HINSTANCE ? ; Instance handle of our program
CommandLine LPSTR ?
.CODE ; Here begins our code
start:
invoke GetModuleHandle, NULL ; get the instance handle of our program.
; Under Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; get the command line. You don't have to call this function IF
; your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; call the main function
invoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain.
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; create local variables on stack
LOCAL msg:MSG
LOCAL hwnd:HWND
mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc
mov wc.style, CS_HREDRAW or CS_VREDRAW
mov wc.lpfnWndProc, OFFSET WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInstance
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 ; register our window class
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 ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area
.WHILE TRUE ; Enter message loop
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; return exit code in eax
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; if the user closes our window
invoke PostQuitMessage,NULL ; quit our application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
分析:
看到一個簡單的 Windows 程序有這麼多行,您是不是有點想撤? 但是您必須要知道的是上面的大多數代碼都是模板而已,模板的意思即是指這些代碼對差不多所有標准 Windows 程序來說都是相同的。在寫 Windows 程序時您可以把這些代碼拷來拷去,當然把這些重復的代碼寫到一個庫中也挺好。其實真正要寫的代碼集中在 WinMain 中。這和一些 C 編譯器一樣,無須要關心其它雜務,集中精力於 WinMain 函數。唯一不同的是 C 編譯器要求您的源代碼有必須有一個函數叫 WinMain。否則 C 無法知道將哪個函數和有關的前後代碼鏈接。相對C,匯編語言提供了較大的靈活性,它不強行要求一個叫 WinMain 的函數。
下面我們開始分析,您可得做好思想准備,這可不是一件太輕松的活。
.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
您可以把前三行看成是"必須"的.
.386告訴MASN我們要用80386指令集。
. model flat,stdcall告訴MASM 我們用的內存尋址模式,此處也可以加入stdcall告訴MASM我們所用的參數傳遞約定。
接下來是函數 WinMain 的原型申明,因為我們稍後要用到該函數,故必須先聲明。我們必須包含 window.inc 文件,因為其中包含大量要用到的常量和結構的定義,該文件是一個文本文件,您可以用任何文本編輯器打開它, window.inc還沒有包含所有的常量和結構定義,不過 hutch 和我一直在不斷加入新的內容。如果暫時在 window.inc 找不到,您也可以自行加入。
我們的程序調用駐扎在 user32.dll (譬如:CreateWindowEx, RegisterWindowClassEx) 和 kernel32.dll (ExitProcess)中的函數,所以必須鏈接這兩個庫。接下來我如果問:您需要把什麼庫鏈入您的程序呢 ? 答案是:先查到您要調用的函數在什麼庫中,然後包含進來。譬如:若您要調用的函數在 gdi32.dll 中,您就要包含gdi32.inc頭文件。和 MASM 相比,TASM 則要簡單得多,您只要引入一個庫,即:import32.lib。<譯者注:但 Tasm5 麻煩的是 windows.inc 非常的不全面,而且如果在 Windows.inc 中包含全部的 API 定義會內存不夠,所以每次你得把用到的 API 定義拷貝出來>
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
接下來是DATA"分段"。 在 .DATA 中我們定義了兩個以 NULL 結尾的字符串 (ASCIIZ):其中 ClassName 是 Windows 類名,AppName 是我們窗口的名字。這兩個變量都是初始化了的。未進行初始化的兩個邊量放在 .DATA? "分段"中,其中 hInstance 代表應用程序的句柄,CommandLine 保存從命令行傳入的參數。HINSTACE 和 LPSTR 是兩個數據類型名,它們在頭文件中定義,可以看做是 DWORD 的別名,之所以要這麼重新定僅是為了易記。您可以查看 windows.inc 文件,在 .DATA? 中的變量都是未經初始化的,這也就是說在程序剛啟動時它們的值是什麼無關緊要,只不過占有了一塊內存,以後可以再利用而已。
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
.....
end start
.DATA "分段"包含了您應用程序的所有代碼,這些代碼必須都在 .code 和 end 之間。至於 label 的命名只要遵從 Windows 規范而且保證唯一則具體叫什麼倒是無所謂。我們程序的第一條語句是調用 GetModuleHandle 去查找我們應用程序的句柄。在Win32下,應用程序的句柄和模塊的句柄是一樣的。您可以把實例句柄看成是您的應用程序的 ID 號。我們在調用幾個函數是都把它作為參數來進行傳遞,所以在一開始便得到並保存它就可以省許多的事。
特別注意:WIN32下的實例句柄實際上是您應用程序在內存中的線性地址。
WIN32 中函數的函數如果有返回值,那它是通過 eax 寄存器來傳遞的。其他的值可以通過傳遞進來的參數地址進行返回。一個 WIN32 函數被調用時總會保存好段寄存器和 ebx,edi,esi和ebp 寄存器,而 ecx和edx 中的值總是不定的,不能在返回是應用。特別注意:從 Windows API 函數中返回後,eax,ecx,edx 中的值和調用前不一定相同。當函數返回時,返回值放在eax中。如果您應用程序中的函數提供給 Windows 調用時,也必須尊守這一點,即在函數入口處保存段寄存器和 ebx,esp,esi,edi 的值並在函數返回時恢復。如果不這樣一來的話,您的應用程序很快會崩潰。從您的程序中提供給 Windows 調用的函數大體上有兩種:Windows 窗口過程和 Callback 函數。
如果您的應用程序不處理命令行那麼就無須調用 GetCommandLine,這裡只是告訴您如果要調用應該怎麼做。
下面則是調用WinMain了。該函數共有4個參數:應用程序的實例句柄,該應用程序的前一實例句柄,命令行參數串指針和窗口如何顯示。Win32 沒有前一實例句柄的概念,所以第二個參數總為0。之所以保留它是為了和 Win16 兼容的考慮,在 Win16下,如果 hPrevInst 是 NULL,則該函數是第一次運行。特別注意:您不用必須申明一個名為 WinMain 函數,事實上在這方面您可以完全作主,您甚至無須有一個和 WinMain 等同的函數。您只要把 WinMain 中的代碼拷到GetCommandLine 之後,其所實現的功能完全相同。在 WinMain 返回時,把返回碼放到 eax 中。然後在應用程序結束時通過 ExitProcess 函數把該返回碼傳遞給 Windows 。
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
上面是WinMain的定義。注意跟在 proc 指令後的parameter:type形式的參數,它們是由調用者傳給 WinMain 的,我們引用是直接用參數名即可。至於壓棧和退棧時的平衡堆棧工作由 MASM 在編譯時加入相關的前序和後序匯編指令來進行。 LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND LOCAL 偽指令為局部變量在棧中分配內存空間,所有的 LOCAL 指令必須緊跟在 PROC 之後。LOCAL 後跟聲明的變量,其形式是 變量名:變量類型。譬如 LOCAL wc:WNDCLASSEX 即是告訴 MASM 為名字叫 wc 的局部邊量在棧中分配長度為 WNDCLASSEX 結構體長度的內存空間,然後我們在用該局部變量是無須考慮堆棧的問題,考慮到 DOS 下的匯編,這不能不說是一種恩賜。不過這就要求這樣申明的局部變量在函數結束時釋放棧空間,(也即不能在函數體外被引用),另一個缺點是您因不能初始化您的局部變量,不得不在稍後另外再對其賦值。
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 hInstance
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 w
上面幾行從概念上說確實是非常地簡單。只要幾行指令就可以實現。其中的主要概念就是窗口類(window class),一個窗口類就是一個有關窗口的規范,這個規范定義了幾個主要的窗口的元素,如:圖標、光標、背景色、和負責處理該窗口的函數。您產生一個窗口時就必須要有這樣的一個窗口類。如果您要產生不止一個同種類型的窗口時,最好的方法就是把這個窗口類存儲起來,這種方法可以節約許多的內存空間。也許今天您不會太感覺到,可是想想以前 PC 大多數只有 1M 內存時,這麼做是非常有必要的。如果您要定義自己的創建窗口類就必須:在一個 WINDCLASS 或 WINDOWCLASSEXE 結構體中指明您窗口的組成元素,然後調用 RegisterClass 或 RegisterClassEx ,再根據該窗口類產生窗口。對不同特色的窗口必須定義不同的窗口類。 WINDOWS有幾個預定義的窗口類,譬如:按鈕、編輯框等。要產生該種風格的窗口無須預先再定義窗口類了,只要包預定義類的類名作為參數調用 CreateWindowEx 即可。
WNDCLASSEX 中最重要的成員莫過於lpfnWndProc了。前綴 lpfn 表示該成員是一個指向函數的長指針。在 Win32中由於內存模式是 FLAT 型,所以沒有 near 或 far 的區別。每一個窗口類必須有一個窗口過程,當 Windows 把屬於特定窗口的消息發送給該窗口時,該窗口的窗口類負責處理所有的消息,如鍵盤消息或鼠標消息。由於窗口過程差不多智能地處理了所有的窗口消息循環,所以您只要在其中加入消息處理過程即可。下面我將要講解 WNDCLASSEX 的每一個成員
WNDCLASSEX STRUCT DWORD
cbSize DWORD ?
style DWORD ?
lpfnWndProc DWORD ?
cbClsExtra DWORD ?
cbWndExtra DWORD ?
hInstance DWORD ?
hIcon DWORD ?
hCursor DWORD ?
hbrBackground DWORD ?
lpszMenuName DWORD ?
lpszClassName DWORD ?
hIconSm DWORD ?
WNDCLASSEX ENDS
cbSize:WNDCLASSEX 的大小。我們可以用sizeof(WNDCLASSEX)來獲得准確的值。
style:從這個窗口類派生的窗口具有的風格。您可以用“or”操作符來把幾個風格或到一起。
lpfnWndProc:窗口處理函數的指針。
cbClsExtra:指定緊跟在窗口類結構後的附加字節數。
cbWndExtra:指定緊跟在窗口事例後的附加字節數。如果一個應用程序在資源中用CLASS偽指令注冊一個對話框類時,則必須把這個成員設成DLGWINDOWEXTRA。
hInstance:本模塊的事例句柄。
hIcon:圖標的句柄。
hCursor:光標的句柄。
hbrBackground:背景畫刷的句柄。
lpszMenuName:指向菜單的指針。
lpszClassName:指向類名稱的指針。
hIconSm:和窗口類關聯的小圖標。如果該值為NULL。則把hCursor中的圖標轉換成大小合適的小圖標。
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
注冊窗口類後,我們將調用CreateWindowEx來產生實際的窗口。請注意該函數有12個參數。
CreateWindowExA proto dwExStyle:DWORD,\
lpClassName:DWORD,\
lpWindowName:DWORD,\
dwStyle:DWORD,\
X:DWORD,\
Y:DWORD,\
nWidth:DWORD,\
nHeight:DWORD,\
hWndParent:DWORD ,\
hMenu:DWORD,\
hInstance:DWORD,\
lpParam:DWORD
我們來仔細看一看這些的參數:
dwExStyle:附加的窗口風格。相對於舊的CreateWindow這是一個新的參數。在9X/NT中您可以使用新的窗口風格。您可以在Style中指定一般的窗口風格,但是一些特殊的窗口風格,如頂層窗口則必須在此參數中指定。如果您不想指定任何特別的風格,則把此參數設為NULL。
lpClassName:(必須)。ASCIIZ形式的窗口類名稱的地址。可以是您自定義的類,也可以是預定義的類名。像上面所說,每一個應用程序必須有一個窗口類。
lpWindowName:ASCIIZ形式的窗口名稱的地址。該名稱會顯示在標題條上。如果該參數空白,則標題條上什麼都沒有。
dwStyle:窗口的風格。在此您可以指定窗口的外觀。可以指定該參數為零,但那樣該窗口就沒有系統菜單,也沒有最大化和最小化按鈕,也沒有關閉按鈕,那樣您不得不按Alt+F4 來關閉它。最為普遍的窗口類風格是 WS_OVERLAPPEDWINDOW。 一種窗口風格是一種按位的掩碼,這樣您可以用“or”把您希望的窗口風格或起來。像 WS_OVERLAPPEDWINDOW 就是由幾種最為不便普遍的風格或起來的。
X,Y: 指定窗口左上角的以像素為單位的屏幕坐標位置。缺省地可指定為 CW_USEDEFAULT,這樣 Windows 會自動為窗口指定最合適的位置。
nWidth, nHeight: 以像素為單位的窗口大小。缺省地可指定為 CW_USEDEFAULT,這樣 Windows 會自動為窗口指定最合適的大小。
hWndParent: 父窗口的句柄(如果有的話)。這個參數告訴 Windows 這是一個子窗口和他的父窗口是誰。這和 MDI(多文檔結構)不同,此處的子窗口並不會局限在父窗口的客戶區內。他只是用來告訴 Windows 各個窗口之間的父子關系,以便在父窗口銷毀是一同把其子窗口銷毀。在我們的例子程序中因為只有一個窗口,故把該參數設為 NULL。
hMenu: WINDOWS菜單的句柄。如果只用系統菜單則指定該參數為NULL。回頭看一看WNDCLASSEX 結構中的 lpszMenuName 參數,它也指定一個菜單,這是一個缺省菜單,任何從該窗口類派生的窗口若想用其他的菜單需在該參數中重新指定。其實該參數有雙重意義:一方面若這是一個自定義窗口時該參數代表菜單句柄,另一方面,若這是一個預定義窗口時,該參數代表是該窗口的 ID 號。Windows 是根據lpClassName 參數來區分是自定義窗口還是預定義窗口的。
hInstance: 產生該窗口的應用程序的實例句柄。
lpParam: (可選)指向欲傳給窗口的結構體數據類型參數的指針。如在MDI中在產生窗口時傳遞 CLIENTCREATESTRUCT 結構的參數。一般情況下,該值總為零,這表示沒有參數傳遞給窗口。可以通過GetWindowLong 函數檢索該值。
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
調用CreateWindowEx成功後,窗口句柄在eax中。我們必須保存該值以備後用。我們剛剛產生的窗口不會自動顯示,所以必須調用 ShowWindow 來按照我們希望的方式來顯示該窗口。接下來調用 UpdateWindow 來更新客戶區。
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
這時候我們的窗口已顯示在屏幕上了。但是它還不能從外界接收消息。所以我們必須給它提供相關的消息。我們是通過一個消息循環來完成該項工作的。每一個模塊僅有一個消息循環,我們不斷地調用 GetMessage 從 Windows 中獲得消息。GetMessage 傳遞一個 MSG 結構體給 Windows ,然後 Windows 在該函數中填充有關的消息,一直到 Windows 找到並填充好消息後 GetMessage 才會返回。在這段時間內系統控制權可能會轉移給其他的應用程序。這樣就構成了Win16 下的多任務結構。如果 GetMessage 接收到 WM_QUIT 消息後就會返回 FALSE,使循環結束並退出應用程序。TranslateMessage 函數是一個是實用函數,它從鍵盤接受原始按鍵消息,然後解釋成 WM_CHAR,在把 WM_CHAR 放入消息隊列,由於經過解釋後的消息中含有按鍵的 ASCII 碼,這比原始的掃描碼好理解得多。如果您的應用程序不處理按鍵消息的話,可以不調用該函數。DispatchMessage 會把消息發送給負責該窗口過程的函數。
mov eax,msg.wParam
ret
WinMain endp
如果消息循環結束了,退出碼存放在 MSG 中的 wParam中,您可以通過把它放到 eax 寄存器中傳給 Windows目前 Windows 沒有利用到這個結束碼,但我們最好還是遵從 Windows 規范已防意外。
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
是我們的窗口處理函數。您可以隨便給該函數命名。其中第一個參數 hWnd 是接收消息的窗口的句柄。uMsg 是接收的消息。注意 uMsg 不是一個 MSG 結構,其實上只是一個 DWORD 類型數。Windows 定義了成百上千個消息,大多數您的應用程序不會處理到。當有該窗口的消息發生時,Windows 會發送一個相關消息給該窗口。其窗口過程處理函數會智能的處理這些消息。wParam 和 lParam 只是附加參數,以方便傳遞更多的和該消息有關的數據。
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
上面可以說是關鍵部分。這也是我們寫 Windows 程序時需要改寫的主要部分。此處您的程序檢查 Windows 傳遞過來的消息,如果是我們感興趣的消息則加以處理,處理完後,在 eax 寄存器中傳遞 0,否則必須調用 DefWindowProc,把該窗口過程接收到的參數傳遞給缺省的窗口處理函數。所有消息中您必須處理的是 WM_DESTROY,當您的應用程序結束時 Windows 把這個消息傳遞進來,當您的應用程序解說到該消息時它已經在屏幕上消失了,這僅是通知您的應用程序窗口已銷毀,您必須自己准備返回 Windows 。在此消息中您可以做一些清理工作,但無法阻止退出應用程序。如果您要那樣做的話,可以處理 WM_CLOSE 消息。在處理完清理工作後,您必須調用 PostQuitMessage,該函數會把 WM_QUIT 消息傳回您的應用程序,而該消息會使得 GetMessage 返回,並在 eax 寄存器中放入 0,然後會結束消息循環並退回 WINDOWS。您可以在您的程序中調用 DestroyWindow 函數,它會發送一個 WM_DESTROY 消息給您自己的應用程序,從而迫使它退出。