天天看點

Win32彙編學習(3):簡單的視窗

這次我們将寫一個 Windows 程式,它會在桌面顯示一個标準的視窗,以此根據代碼來學習如何建立一個簡單的視窗。

理論:

Windows 程式中,在寫圖形使用者界面時需要調用大量的标準 Windows Gui 函數。其實這對使用者和程式員來說都有好處,對于使用者,面對的是同一套标準的視窗,對這些視窗的操作都是一樣的,是以使用不同的應用程式時無須重新學習操作。對程式員來說,這些 Gui 源代碼都是經過了微軟的嚴格測試,随時拿來就可以用的。當然至于具體地寫程式對于程式員來說還是有難度的。為了建立基于視窗的應用程式,必須嚴格遵守規範。做到這一點并不難,隻要用子產品化或面向對象的程式設計方法即可。

下面我就列出在桌面顯示一個視窗的幾個步驟:

  1. 得到您應用程式的句柄(必需);
  2. 得到指令行參數(如果您想從指令行得到參數,可選);
  3. 注冊視窗類(必需,除非您使用 Windows 預定義的視窗類,如 MessageBox 或 dialog box;
  4. 産生視窗(必需);
  5. 在桌面顯示視窗(必需,除非您不想立即顯示它);
  6. 重新整理視窗客戶區;
  7. 進入無限的擷取視窗消息的循環;
  8. 如果有消息到達,由負責該視窗的視窗回調函數處理;
  9. 如果使用者關閉視窗,進行退出處理。

相對于單使用者的 DOS 下的程式設計來說,Windows 下的程式架構結構是相當複雜的。但是 Windows 和 DOS 在系統架構上是截然不同的。Windows 是一個多任務的作業系統,故系統中同時有多個應用程式彼此協同運作。這就要求 Windows 程式員必須嚴格遵守程式設計規範,并養成良好的程式設計風格。

内容:

下面是我們簡單的視窗程式的源代碼。在進入複雜的代碼前,指出幾點要點:

  • 您應當把程式中要用到的所有常量和結構體的聲明放到一個頭檔案中,并且在源程式的開始處包含這個頭檔案。這麼做将會節省您大量的時間,也免得一次又一次的敲鍵盤。目前,我所使用的是masm32.com提供的。您也可以定義您自己的常量和結構體,但最好把它們放到獨立的頭檔案中
  • 用 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 檔案,因為其中包含大量要用到的常量和結構的定義,該檔案是一個文本檔案,您可以用任何文本編輯器打開并且檢視它

我們的程式調用 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
           

.CODE

"分段"包含了您應用程式的所有代碼,這些代碼必須都在 .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

    就是由幾種最為普遍的風格

    or

    起來的。
  • 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

才會傳回。在這段時間内系統控制權可能會轉移給其他的應用程式。這樣就構成了Windows 下的多任務結構。如果

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 消息給您自己的應用程式,進而迫使它退出。

轉載于:https://www.cnblogs.com/Akkuman/p/8424832.html

繼續閱讀