Win32匯編--分析窗口程序
分析窗口程序
了解了消息驅(qū)動(dòng)體系的工作流程以后,讓我們來(lái)分析如何用Win32匯編實(shí)現(xiàn)這一切。
模塊和句柄
模塊的概念
一個(gè)模塊代表的是一個(gè)運(yùn)行中的exe文件或dll文件,用來(lái)代表這個(gè)文件中所有的代碼和資源,磁盤上的文件不是模塊,裝入內(nèi)存后運(yùn)行時(shí)就叫做模塊。
一個(gè)應(yīng)用程序調(diào)用其他DLL中的API時(shí),這些DLL文件被裝入內(nèi)存,就產(chǎn)生了不同的模塊,為了區(qū)分地址空間中的不同模塊,每個(gè)模塊都有一個(gè)唯一的模塊句柄來(lái)標(biāo)識(shí)。
很多API函數(shù)中都要用到程序的模塊句柄,以便利用程序中的各種資源,所以在程序中一開始就先取得模塊句柄并存放到一個(gè)全局變量中可以省去很多的麻煩,在Win32中,模塊句柄在數(shù)值上等于程序在內(nèi)存中裝入的起始地址。
取模塊句柄使用的API函數(shù)是GetModuleHandle,它的使用方法是:
invoke GetModuleHandle, lpModuleName
lpModuleName參數(shù)是一個(gè)指向含有模塊名稱字符串的指針,可以用這個(gè)函數(shù)取得程序地址空間中各個(gè)模塊的句柄,例如,如果想得到User32.dll的句柄以便使用其中包含的圖標(biāo)資源,那么可以如下使用:
szUserDll db ‘User32.dll’,0
invoke GetModuleHandle, addr szUserDll
.if eax
mov hUserDllHandle,eax
.endif
如果使用參數(shù)NULL,調(diào)用GetModuleHandle,那么得到的是調(diào)用者本模塊的句柄,如下所示:
invoke GetModuleHandl,NULL
mov hInstance,eax
可以注意到,把返回的句柄放到了hInstance變量里而并不是放在hModule中,為什么是hInstance呢?Instance是“實(shí)例”,它的概念來(lái)自于Win16,Win16中不同運(yùn)行程序的地址空間并不是完全隔離的,一個(gè)可執(zhí)行文件運(yùn)行后形成“模塊”,多次加載同一個(gè)可執(zhí)行文件時(shí),這個(gè)“模塊”是公用的,為了區(qū)分多次加載的“拷貝”,就把每個(gè)“拷貝”叫做實(shí)例,每個(gè)實(shí)例均用不同的“實(shí)例句柄”(hInstance)值來(lái)標(biāo)識(shí)它們。
但在Win32中,程序運(yùn)行時(shí)是隔離的,每個(gè)實(shí)例都使用自己私有的4GB空間,都認(rèn)為自己是唯一的,不存在一個(gè)模塊的多個(gè)實(shí)例的問題,實(shí)際上在Win32中,實(shí)例句柄就是模塊句柄,但很多API原型中用到模塊句柄的時(shí)候使用的名稱還是沿用hInstance,所以我們還是把變量名稱取為hInstance。
在C++語(yǔ)言的編程中,hInstance通過WinMain由系統(tǒng)傳入,WinMain的原型是:
WinMain(hInstance, hPrevInstance, lpszCmdParam, nCmdShow)
程序不用自己去獲得hInstance,但在Win32匯編中必須自己獲取,如果不了解hModule就是hInstance的話,就無(wú)法得知如何得到hInstance,因?yàn)椴]有一個(gè)類似于GetInstanceHandle之類的API函數(shù)。
句柄是什么
隨著分析的深入,句柄(handle)一詞也出現(xiàn)得頻繁了起來(lái),“句柄”是什么呢?句柄只是一個(gè)數(shù)值而已,它的值對(duì)程序來(lái)說(shuō)是沒有意義的,它只是Windows用來(lái)表示各種資源的編號(hào)而已,所以只有Windows才知道怎么使用它來(lái)引用各種資源。
舉例說(shuō)明,屏幕上已經(jīng)有10窗口,Windows把它們從1到10編號(hào),應(yīng)用程序又建立了一個(gè)窗口,現(xiàn)在Windows把它編號(hào)為11,然后把11當(dāng)做窗口句柄返回給應(yīng)用程序,應(yīng)用程序并不知道11代表的是什么,但在操作窗口的時(shí)候,把11當(dāng)做句柄傳給Windows,Windows自然可以根據(jù)這個(gè)數(shù)值查出是哪個(gè)窗口。當(dāng)該窗口關(guān)閉的時(shí)候,11這個(gè)編號(hào)作廢。第二次運(yùn)行的時(shí)候,如果屏幕上現(xiàn)有5個(gè)窗口,那么現(xiàn)在句柄可能就是6了,所以,應(yīng)用程序并不用關(guān)心句柄的具體數(shù)值是多少。打個(gè)比方,可以把句柄當(dāng)做是商場(chǎng)中寄放書包時(shí)營(yíng)業(yè)員給的紙條,紙條上的標(biāo)記用戶并不知道是什么意思,但把它交還給營(yíng)業(yè)員的時(shí)候,她自然會(huì)找到正確的書包。
Windows中幾乎所有的東西都是用句柄來(lái)標(biāo)識(shí)的,文件句柄、窗口句柄、線程句柄和模塊句柄等,同樣道理,不必關(guān)心它們的值究竟是多少,拿來(lái)用就是了!
創(chuàng)建窗口
在創(chuàng)建窗口之前,先要談到“類”。“類”的概念讀者都不陌生,主要是為了把一組物體的相同屬性歸納整理起來(lái)封裝在一起,以便重復(fù)使用,在“類”已定義的屬性基礎(chǔ)上加上其他個(gè)性化的屬性,就形成了各式各樣的個(gè)體。
Windows中創(chuàng)建窗口同樣使用這樣的層次結(jié)構(gòu)。首先定義一個(gè)窗口類,然后在窗口類的基礎(chǔ)上添加其他的屬性建立窗口。不同一步到位的辦法是因?yàn)楹芏啻翱诘幕緦傩院托袨槎际且粯拥模绨粹o、文本輸入框和選擇框等,對(duì)這些東西Windows都預(yù)定義了對(duì)應(yīng)的類,使用時(shí)直接使用對(duì)應(yīng)的類名建立窗口就可以了。只有用戶自定義的窗口才需要先定義自己的類,再建立窗口。這樣可以節(jié)省資源。
注冊(cè)窗口類
建立窗口類的方法是在系統(tǒng)中注冊(cè),注冊(cè)窗口類的API函數(shù)是RegisterClassEx,最后的Ex是擴(kuò)展的意思,因?yàn)樗?/span>Win16中RegisterClass的擴(kuò)展。一個(gè)窗口類定義了窗口的一些主要屬性,如:圖標(biāo)、光標(biāo)、背景色、菜單和負(fù)責(zé)處理該窗口所屬消息的函數(shù)。這些屬性并不是分成多個(gè)參數(shù)傳遞過去的,而是定義在一個(gè)WNDCLASSEX結(jié)構(gòu)中,再把結(jié)構(gòu)的地址當(dāng)參數(shù)一次性傳遞給RegisterClassEx,WNDCLASSEX是WNDCLASS結(jié)構(gòu)的擴(kuò)展。
WNDCLASSEX的結(jié)構(gòu)定義為:
WNDCLASSEX STRUCT
CbSize DWORD ? ;結(jié)構(gòu)的字節(jié)數(shù)
Style DWORD ? ;類風(fēng)格
LpfnWndProc DWORD ? ;窗口過程的地址
CbClsExtra DWORD ?
CbWndExtra DWORD ?
HInstance DWORD ? ;所屬的實(shí)例句柄
HIcon DWORD ? ;窗口圖標(biāo)
HCursor DWORD ? ;窗口光標(biāo)
HbrBackground DWORD ? ;背景色
LpszMenuName DWORD ? ;窗口菜單
LpszClassName DWORD ? ;類名字符串的地址
HIconSm DWORD ? ;上圖標(biāo)
WNDCLASSEX ENDS
在Win32匯編源程序中,注冊(cè)窗口類的代碼如下:
local @stWndClass:WNDCLASSEX ;定義一個(gè)WNDCLASSEX結(jié)構(gòu)
invoke RtlZeroMemory, addr @stWndClass, sizeof @stWndClass
invoke LoadCursor, 0, IDC_ARROW
mov @stWndClass.hCursor, eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize, sizeof WNDCLASSEX
mov @stWndClass.style, CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc, offset _ProcWinMain
mov @stWndClass.hbrBackground, COLOR_WINDOW + 1
mov @stWndClass.lpszClassName, offset szClassName
invoke RegisterClassEx, addr @stWndClass
程序定義了一個(gè)WNDCLASSEX結(jié)構(gòu)的變量@stWndClass,用RtlZeroMemory將它填為全零,再填寫結(jié)構(gòu)的各個(gè)字段,這樣,沒有賦值的部分就保持為0,結(jié)構(gòu)各字段的含義如下:
hIcon 圖標(biāo)句柄,指定顯示在窗口標(biāo)題欄左上角的圖標(biāo)。Windows已經(jīng)預(yù)定課外作業(yè)了一些圖標(biāo),同樣,程序也可以使用在資源文件中定義的圖標(biāo),這些圖標(biāo)的句柄可以用LoadIcon函數(shù)獲得。例子程序沒有用到圖標(biāo),所以Windows給窗口顯示了一個(gè)默認(rèn)的圖標(biāo)。
hCursor 光標(biāo)句柄,指定了鼠標(biāo)在窗口中光標(biāo)形狀。同樣,Windows也預(yù)定義了一些光標(biāo),可以用LoadCursor獲取它們的句柄,IDC_ARROW是Windows預(yù)定義的箭頭光標(biāo),如果想使用自定義的光標(biāo),也可以自己在資源文件中定義。
lpszMenuName 指定窗口上顯示的默認(rèn)菜單,它指向一個(gè)字符串,描述資源文件中菜單的名稱,如果資源文件中菜單是用數(shù)值定義的,那么這里使用菜單資源的數(shù)值。窗口中的菜單也可以在建立窗口函數(shù)CreateWindowsEx的參數(shù)中指定。如果在兩個(gè)地方都沒有指定,那么建立的窗口上就沒有菜單。
hInstance 指定要注冊(cè)的窗口類屬于哪個(gè)模塊,模塊句柄在程序開始的地方已經(jīng)用GetModuleHandle函數(shù)獲得。
cbSize 指定WNDCLASSEX結(jié)構(gòu)的長(zhǎng)度,用sizeof偽操作符來(lái)獲取。很多Win32 API參數(shù)中的結(jié)構(gòu)都有cbSize字段,它主要是用來(lái)區(qū)分結(jié)構(gòu)的版本,當(dāng)以后新增了一個(gè)字段時(shí),cbSize就相應(yīng)增大,如果調(diào)用的時(shí)候cbSize還是老的長(zhǎng)度,表示運(yùn)行的是基于舊結(jié)構(gòu)的程序,這樣可以防止使用無(wú)效的字段。
style 窗口風(fēng)格。CS_HREDRAW和CS_VREDRAW表示窗口的寬度或高度改變時(shí)是否重畫窗口。比較重要的是CS_DBLCLKS風(fēng)格,指定了它,Windows才會(huì)把在窗口中快速兩次單擊鼠標(biāo)的行為翻譯為雙擊消息WM_LBUTTONDBLCLK發(fā)給窗口過程。
hbrBackground 窗口客戶區(qū)的背景色。前面的hbr表示它是一個(gè)刷子(Brush)的句柄,刷子一詞形象地表示了填充一個(gè)區(qū)域的著色模式。Windows預(yù)定義了一些刷子,如BLACK_BRUSH和WHITE_BRUSH等,可以用下列語(yǔ)句來(lái)得到它們的句柄:
invoke GetStockObject, WHITE_BRUSH
但在這里也可以使用顏色值,Windows已經(jīng)預(yù)定義了一些顏色值,分別對(duì)應(yīng)窗口各部分的顏色,如COLOR_BACKGROUND,COLOR_HIGHLIGHT,COLOR_MENU和COLOR_WINDOWS等,使用顏色值的時(shí)候,Windows規(guī)定必須在顏色值上加1,所以在程序中的指令是:
mov @stWndClass.hbrBackground, COLOR_WINDOWS + 1
lpszClassName指定程序員要建立的類命名,以便以后用這個(gè)名稱來(lái)引用它。這個(gè)字段是一個(gè)字符串指針,在程序里,它指向MyClass字符串中。
cbWndExtra和cbClsExtra分別是在Windows內(nèi)部保存的窗口結(jié)構(gòu)和類結(jié)構(gòu)中給程序員預(yù)留的空間大小,用來(lái)存放自定義的數(shù)據(jù),它們的單位是字節(jié)。不使用自定義數(shù)據(jù)的話,這兩個(gè)字段就是0。
lpfnWndProc 是最重要的參數(shù),它指定了基于這個(gè)類建立的窗口的窗口過程地址。通過這個(gè)參數(shù),Windows就知道了在DispatchMessage函數(shù)中把窗口消息發(fā)到哪里去,一個(gè)窗口過程可以為多個(gè)窗口服務(wù),只要這些窗口是基于同一個(gè)窗口類建立的。Windows中不同應(yīng)用程序中的按鈕和文本框的行為都是一樣的,就是因?yàn)樗鼈兪腔谙嗤?/span>Windows預(yù)定義類建立的,它們背后的窗口過程其實(shí)是同一段代碼。
結(jié)構(gòu)中的style表示窗口的風(fēng)格,Windows已經(jīng)有一些預(yù)定義的值,它們是以CS_(Class Style的縮寫)開始的標(biāo)識(shí)符,如下所示:
CS_VREDRAW 00000001H
CS_HREDRAW 00000002H
CS_KEYCVTWINDOWS 00000004H
CS_DBLCLKS 00000008H
CS_OWNDC 00000020H
CS_CLASSDC 00000040H
可以看到,這些預(yù)定義值實(shí)際上在使用不重復(fù)的數(shù)據(jù)位,所以可以組合起來(lái)使用,同時(shí)使用不同的預(yù)定義值并不會(huì)引起混淆。
注意:對(duì)于不同二進(jìn)制位組合的計(jì)算,“加”和“或”的結(jié)果是一樣的,在FirstWindows程序中用CS_HREDRAW or CS_VREDRAW來(lái)代表兩個(gè)組合,若用CS_HREDRAW + CS_VREDRAW也并沒有什么不同,但強(qiáng)烈建議使用or,因?yàn)槿绻恍⌒闹付藘蓚€(gè)同樣的風(fēng)格時(shí):CS_HREDRAW or CS_VREDRAW or CS_VREDRAW和原來(lái)的數(shù)值是一樣的,而CS_HREDRAW + CS_VREDRAW + CS_VREDRAW就不對(duì)了,因?yàn)?/span>1 or 1 = 1,而1 + 1就等于2了。
建立窗口
接下來(lái)的步驟是在已經(jīng)注冊(cè)的窗口類的基礎(chǔ)上建立窗口,使用“類”的原因是定義窗口的“共性”,建立窗口時(shí)肯定還要指定窗口的很多“個(gè)性化”的參數(shù),如WNDCLASSEX結(jié)構(gòu)中沒有定義的外觀、標(biāo)題、位置、大小和邊框類型等屬性,這些屬性是在建立窗口時(shí)才指定的。
和注冊(cè)窗口類時(shí)用一個(gè)結(jié)構(gòu)傳遞所有參數(shù)不同,建立窗口時(shí)所有的屬性都是用單個(gè)參數(shù)的方式傳遞的,建立窗口的函數(shù)是CreateWindowEx(注意不要寫成CreateWindowsEx),同樣,它是Win16中的CreateWindow函數(shù)的擴(kuò)展,主要表現(xiàn)在多了一個(gè)dwExStyle(擴(kuò)展風(fēng)格)參數(shù),原因是Win32比Win16中多了很多種窗口風(fēng)格,原來(lái)的一個(gè)風(fēng)格參數(shù)已經(jīng)不夠用了。
CreateWindowEx函數(shù)的使用方法是:
invoke CreateWindowEx, dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam
12個(gè)參數(shù)很好理解:
lpClassName 建立窗口使用的類名字符串指針,在FirstWindow程序中指向MyClass字符串,表示使用MyClass類建立窗口,這正是我們自己注冊(cè)的類,這樣一來(lái),這個(gè)窗口就有“MyClass”的所有屬性,并且消息將被發(fā)到“MyClass”中指定的窗口過程中去,當(dāng)然,這里也可以是Window預(yù)定義的類名。
lpWindowName 指向表示窗口名稱的字符串,該名稱會(huì)顯示在標(biāo)題欄上。如果該參數(shù)空白,則標(biāo)題欄上什么都沒有。
hMenu 窗口上要出現(xiàn)的菜單的句柄。在注冊(cè)窗口類的時(shí)候也定義了一個(gè)菜單,那是窗口的默認(rèn)菜單,意思是如果這里沒有定義菜單(用參數(shù)NULL)而注冊(cè)窗口類時(shí)定義了菜單,則使用窗口類中定義的菜單;如果這里指定了菜單句柄,則不管窗口類中有沒有定義都將使用這里定義的菜單;兩個(gè)地方都沒有定義菜單句柄,則窗口上沒有菜單。另外,當(dāng)建立的窗口是子窗口時(shí)(dwStyle中指定了WS_CHILD),這個(gè)參數(shù)是另一個(gè)含義,這時(shí)hMenu參數(shù)指定的是子窗口的ID號(hào),這樣可以節(jié)省一個(gè)參數(shù)的位置,因?yàn)榉凑哟翱诓粫?huì)有菜單。
hpParam 這是一個(gè)指針,指向一個(gè)欲傳給窗口的參數(shù),這個(gè)參數(shù)在WM_CREATE消息中可以被獲取,一般情況下用不到這個(gè)字段。
hInstance 模塊句柄,和注冊(cè)窗口類時(shí)一樣,指定了窗口所屬的程序模塊。
hWndParent 窗口所屬的父窗口,對(duì)于普通窗口(相對(duì)于子窗口),這里的“父子”關(guān)系只是從屬關(guān)系,主要用來(lái)在父窗口銷毀時(shí)一同將其“子”窗口銷毀,并不會(huì)把窗口位置限制在父窗口的客戶區(qū)范圍內(nèi),但如果要建立的是真正的子窗口(dwStyle中指定了WS_CHILD的時(shí)候),這時(shí)窗口位置會(huì)被限制在父窗口的客戶區(qū)范圍內(nèi),同時(shí)窗口的坐標(biāo)(x,y)也是以父窗口的左上角為基準(zhǔn)的。
x,y 指定窗口左上角位置,單位是像素。默認(rèn)時(shí)可指定為CW_USEDEFAULT,這樣Windows會(huì)自動(dòng)為窗口指定最合適的位置,當(dāng)建立子窗口時(shí),位置是以父窗口的左上角為基準(zhǔn)的,否則,以屏幕左上角為基準(zhǔn)。
nWidth,hHeight 窗口的寬度和高度,也就是窗口的大小,同樣是以像素為單位的。默認(rèn)時(shí)可指定為CW_USEDEFAULT,這樣Windows會(huì)自動(dòng)為窗口指定最合適的大小。
窗口的兩個(gè)參數(shù)dwStyle和dwExStyle決定了窗口的外形和行為,dwStyle是從Win16開始就有的屬性,下表列出了一些常見的dwStyle定義,它們是一些以WS(Windows Style的縮寫)為開頭的預(yù)定義值。
|
預(yù)定義值 |
16進(jìn)制值 |
含義 |
|
WS_OVERLAPPED |
00000000h |
普通的重疊式窗口 |
|
WS_POPUP |
80000000h |
彈出式窗口(沒有標(biāo)題欄) |
|
WS_CHILD |
40000000h |
子窗口 |
|
WS_MINIMIZE |
20000000h |
初始狀態(tài)是最小化的 |
|
WS_VISIBLE |
10000000h |
初始狀態(tài)是可見的 |
|
WS_DISABLED |
08000000h |
初始狀態(tài)是被禁止的 |
|
WS_MAXIMIZE |
01000000h |
初始狀態(tài)是最大化的 |
|
WS_BORDER |
00800000h |
單線條邊框 |
|
WS_DLGFRAME |
00400000h |
對(duì)話框類型的邊框 |
|
WS_VSCROLL |
00200000h |
帶垂直滾動(dòng)條 |
|
WS_HSCROLL |
00100000h |
帶水平滾動(dòng)條 |
|
WS_SYSMENU |
00080000h |
帶系統(tǒng)菜單(即帶標(biāo)題欄左上角的圖標(biāo)) |
|
WS_THICKFRAME |
00040000h |
可以拖動(dòng)調(diào)整大小的邊框 |
為了容易理解,Windows也為一些定義取了一些別名,同時(shí),由于窗口的風(fēng)格往往是幾種風(fēng)格的組合,所以Windows也預(yù)定義了一些組合值,如下表所示:
|
預(yù)定義值 |
等效值 |
|
WS_CHILDWINDOW |
WS_CHILD |
|
WS_TILED |
WS_OVERLAPPED |
|
WS_ICONIC |
WS_MINIMIZE |
|
WS_SIZEBOX |
WS_THICKFRAME |
|
WS_OVERLAPPEDWINDOW |
WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_THICKFRAME or WS_MINIMIZEBOX or WS_MAXINIZEBOX |
|
WS_TILEDWINDOW |
WS_OVERLAPPEDWINDOW |
|
WS_POPUPWINDOW |
WS_POPUP or WS_BORDER or WS_SYSMENU |
dwExStyle是Win32中擴(kuò)展的,它們是一些以WS_EX_開頭的預(yù)定義值,主要定義了一些特殊的風(fēng)格,如下表所示:
|
預(yù)定義值 |
16進(jìn)制值 |
含義 |
|
WS_EX_POPMOST |
00000008h |
總在頂層的窗口 |
|
WS_EX_ACCEPTFILES |
00000010h |
允許窗口進(jìn)行鼠標(biāo)拖放操作 |
|
WS_EX_TOOLWINDOW |
00000080h |
工具窗口(很窄的標(biāo)題欄) |
|
WS_EX_WINDOWEDGE |
00000100h |
立體體的邊框 |
|
WS_EX_CLIENTEDGE |
00000200h |
客戶區(qū)立體邊框 |
|
WS_EX_OVERLAPPDWINDOW |
|
WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE |
|
WS_EX_PALETTEWINDOW |
|
WS_EX_POPMOST |
用預(yù)定義的組合值WS_EX_PALETTEWINDOW可以很方便地構(gòu)成浮在其他窗口前面的工具欄。
建立窗口的相關(guān)代碼如下:
invoke CreateWindowEx, WS_EX_CLIENTEDGE, \
offset szClassName, offset szCaptionMain, \
WS_OVERLAPPPEDWINDOW, \
100, 100, 600, 400, \
NULL, NULL, hInstance, NULL
mov hWinMain,eax
invoke ShowWindow, hWinMain, SW_SHWONORMAL
invoke UpdateWindow, hWinMain
建立窗口以后,eax中傳回來(lái)的是窗口句柄,要把它保存起來(lái),這時(shí)候,窗口雖已建立,但還沒有在屏幕上顯示出來(lái),要用ShowWindow把它顯示出來(lái),ShowWindow也可以用在另的地方,主要用來(lái)控制窗口的顯示狀態(tài)(顯示或隱藏),大小控制(最大化、最小化或原始大小)和是否激活(當(dāng)前窗口還是背后的窗口),它用窗口句柄第一個(gè)參數(shù),第二個(gè)參數(shù)則是顯示的方式。顯示方式有如下的預(yù)定義值:
|
預(yù)定義值 |
等效值 |
|
SW_HIDE |
隱藏窗口,大小不變,激活狀態(tài)不變 |
|
SW_MAXIMIZE |
最大化窗口,顯示狀態(tài)不變,激活狀態(tài)不變 |
|
SW_MINIMIZE |
最小化窗口,顯示狀態(tài)不變,激活狀態(tài)不變 |
|
SW_RESTORE |
從最大化或最小化恢復(fù)正常大小,顯示狀態(tài)不變,激活狀態(tài)不變 |
|
SW_SHOW |
顯示并激活窗口,大小狀態(tài)不變 |
|
SW_SHOWMAXIMIZED |
顯示并激活窗口,以最大化顯示 |
|
SW_SHOWMINIMIZED |
顯示并激活窗口,以最小化顯示 |
|
SW_SHOWMINOACTIVE |
顯示窗口并最小化,激活狀態(tài)不變 |
|
SW_SHOWNA |
顯示窗口,大小狀態(tài)不變,激活狀態(tài)不變 |
|
SW_SHOWNOACTIVATE |
顯示并從最大化或最小化恢復(fù)正常大小,激活狀態(tài)不變 |
|
SW_SHOWNORMAL |
顯示并激活窗口,恢復(fù)正常大小(初始化時(shí)用這個(gè)參數(shù)) |
窗口顯示以后,用UpdateWindow繪制客戶區(qū),它實(shí)際上就是向窗口發(fā)送了一條WM_PAINT消息。到此為止,一個(gè)頂層窗口就正常建立并顯示了。
CreateWindowEx也可以用來(lái)建立子窗口,Windows中有很多預(yù)定義的子窗口類,如按鈕和文本框的類名分別是Button和Edit。要建立一個(gè)按鈕,只要把lpClassName指向Button字符串就可以了。例如:
.data
szButton db ‘button’,0
szButtonText db ‘&OK’,0
invoke CreateWindowEx, NULL, \
offset szButton, offset szButtonText, \
WS_CHILD or WS_VISIBLE, \
10, 10, 65, 22, \
hWnd, 1, hInstance, NULL
在FirstWindow的源程序中加入按鈕類的定義字符串“szButton”和按鈕文字字符串“szButtonText”,然后在窗口過程的WM_CREATE消息中加入建立按鈕的代碼,執(zhí)行一下,窗口中就出現(xiàn)了一個(gè)按鈕。建立按鈕的時(shí)候,lpWindowName參數(shù)就是按鈕上的文字,風(fēng)格則一定要指定WS_CHILD,建立的按鈕才會(huì)在我們的主窗口上,WS_VISIBLE也要同時(shí)指定,否則按鈕不會(huì)顯示出來(lái),hMenu參數(shù)在這里用做表示子窗口ID,將它設(shè)置為1,在建立多個(gè)子窗口的時(shí)候,ID應(yīng)該有所區(qū)別。
消息循環(huán)
消息循環(huán)的一般形式:
.while TRUE
invoke GetMessage, addr @stMsg, NULL, 0, 0
.break .if eax == 0
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
.endw
消息循環(huán)中的幾個(gè)函數(shù)要用到一個(gè)MSG結(jié)構(gòu),用來(lái)做消息傳遞:
MSG STRUCT
Hwnd DWORD ?
Message DWORD ?
WParam DWORD ?
LParam DWORD ?
Time DWORD ?
Pt POINT <>
MSG ENDS
各個(gè)字段的含義是:
hwnd 消息要發(fā)向的窗口句柄。
message 消息標(biāo)識(shí)符,在頭文件中以WM_開頭的預(yù)定義值(意思為Windows Message)。
wParam 消息的參數(shù)之一。
lParam 消息的參數(shù)之二。
time 消息放入消息隊(duì)列的時(shí)間。
pt 這是一個(gè)POINT的數(shù)據(jù)結(jié)構(gòu),表示消息放入消息隊(duì)列時(shí)的鼠標(biāo)坐標(biāo)。
這個(gè)結(jié)構(gòu)定義了消息的所有屬性,GetMessage函數(shù)就是從消息隊(duì)列中取出這樣一條消息來(lái)的:
invoke GetMessage, lpMsg, hWnd, wMsgPilterMin, wMsgFilterMax
函數(shù)的lpMsg指向一個(gè)MSG結(jié)構(gòu),函數(shù)會(huì)在這里返回取到的消息,hWnd參數(shù)指定要獲取哪個(gè)窗口的消息,例子中指定為NULL,表示獲取的是所有本程序所屬窗口的消息,wMsgFilterMin和wMsgFilterMax為0表示獲取所有編號(hào)的消息。
GetMessage函數(shù)從消息隊(duì)列里取得消息,填寫好MSG結(jié)構(gòu)并返回,如果獲取的消息是WM_QUIT消息,那么eax中的返回值是0,否則eax返回非零值,所以用.break .if eax == 0來(lái)檢查返回值,如果消息隊(duì)列中有WM_QUIT則退出消息循環(huán)。
TranslateMessage將MSG結(jié)構(gòu)傳給Windows進(jìn)行一些鍵盤消息的轉(zhuǎn)換,當(dāng)有鍵盤按下和放開時(shí),Windows產(chǎn)生WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息,但這些消息的參數(shù)中包含的是按鍵的掃描碼,轉(zhuǎn)換成常用的ASCII碼要經(jīng)過查表,很不方便,TranslateMessage遇到鍵盤消息則將掃描碼轉(zhuǎn)換成ASCII碼并在消息隊(duì)列中插入WM_CHAR或WM_SYSCHAR消息,參數(shù)就是轉(zhuǎn)換好的ASCII碼,如此一來(lái),要處理鍵盤消息的話只要處理WM_CHAR消息就好了。遇到別的消息則TranslateMessage不做處理。
最后,由DispatchMessage將消息發(fā)送到窗口對(duì)應(yīng)的窗口過程去處理。窗口過程返回后DispatchMessage函數(shù)才返回,然后開始新一輪消息循環(huán)。
其它形式的消息循環(huán):
GetMessage函數(shù)是程序空閑的時(shí)候主動(dòng)將控制權(quán)交還給Windows的一種方式,Windows是一個(gè)搶占式的多任務(wù)系統(tǒng),任務(wù)之間每20ms切換一次,試想一下,如果窗口程序在主窗口中采用死循環(huán)等待,消息由Windows直接發(fā)送到窗口過程,那么程序會(huì)是下列這種樣子:
invoke CreateWindow,
invoke ShowWindow,
invoke UpdateWindow,
.while dwQuitFlag == 0 ;要退出時(shí)在窗口過程中設(shè)置dwQuitFlag
.endw
invoke ExitProcess,
但這樣一來(lái),即使程序在空閑狀態(tài),輪到自己的20ms時(shí)間片的時(shí)候,CPU時(shí)間就會(huì)全部消耗在.while循環(huán)中,使用GetMessage的時(shí)候,輪到應(yīng)用程序時(shí)間片的時(shí)候,如果消息隊(duì)列里還沒有消息,那么程序還是停留在GetMessage內(nèi)部,這時(shí)就可以由Windows當(dāng)家作主沒收這20ms的時(shí)間片,如此保證了CPU資源的合理應(yīng)用。
如果應(yīng)用程序想把所有時(shí)間充分用回來(lái),消息隊(duì)列里沒有消息的時(shí)候不讓GetMessage在Windows內(nèi)部等待,拱手交出屬于自己的CPU時(shí)間,那么消息循環(huán)可以是下列這種樣子:
.while TRUE
invoke PeekMessage, addr @stMsg, NULL, 0, 0, PM)REMOVE
.if eax
.break .if @stMsg.message == WM_QUIT
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
.else
<做其他工作>
.endif
.endw
PeekMessage是一個(gè)類似于GetMessage的函數(shù),區(qū)別在于當(dāng)消息隊(duì)列里有消息的時(shí)候,PeekMessage取回消息,并在eax中返回非零值,沒有消息的時(shí)候它會(huì)直接返回,并在eax中返回零。所以在返回非零值的時(shí)候,程序檢查消息是否是WM_QUIT,是則結(jié)束消息循環(huán),不是則用標(biāo)準(zhǔn)流程處理消息;返回零的時(shí)候,表示是空閑時(shí)間,程序就可以做其他工作了,但插入做其他工作的代碼執(zhí)行時(shí)間不能過長(zhǎng),以不越過10ms為好,否則會(huì)影響正常的消息處理,使窗口的反應(yīng)看起來(lái)很遲鈍。如果必須處理很長(zhǎng)時(shí)間的工作,那么應(yīng)該將它分成很多小部分處理,以便有足夠的頻率來(lái)用PeekMessage來(lái)檢查消息。PeekMessage的前面4個(gè)參數(shù)和GetMessage是相同的,增加了最后一個(gè)參數(shù),PM_REMOVE表示取回消息的同時(shí)從消息隊(duì)列里刪除,否則用PM_NOREMOVE。
窗口過程
窗口過程是給Windows回調(diào)用的,它必須遵循規(guī)定的格式。對(duì)窗口過程的子程序名并沒有規(guī)定,對(duì)Windows來(lái)說(shuō),窗口過程的地址才是唯一需要的,例子程序中的子程序名是_ProcWinMain,讀者可以改用任何名稱。窗口過程子程序的參數(shù)格式為:
WindowProc proc hwnd, uMsg, wParam, lParam
第一個(gè)參數(shù)是窗口句柄,一個(gè)窗口過程可能為多個(gè)基于同一個(gè)窗口類的窗口服務(wù),所以Windows回調(diào)的時(shí)候必須指出要操作的窗口,否則窗口過程不知道要去處理哪個(gè)窗口,FirstWindow程序只建立了一個(gè)窗口,所以每次傳遞過來(lái)的hwnd和用CreateWindowEx函數(shù)返回的窗口句柄是一樣的;
第二個(gè)參數(shù)是消息標(biāo)識(shí),后面兩個(gè)參數(shù)是消息的兩個(gè)參數(shù)。這4個(gè)參數(shù)和消息循環(huán)中MSG結(jié)構(gòu)中的前4個(gè)字段是一樣的。
窗口過程的結(jié)構(gòu)
窗口過程一般有如下結(jié)構(gòu):
WindowsProc proc uses ebx edi esi, hWnd, uMsg, wParam, lParam
mov eax,uMsg
.if eax == WM_XXX
<處理WM_XXX消息>
.elseif eax == WM_YYY
<處理WM_YYY消息>
.elseif eax == WM_CLOSE
invoke DestroyWindow, hWinMain
invoke PostQuitMessage, NULL
.else
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.endif
mov eax,eax
ret
WindowProc endp
該過程主要是對(duì)uMsg參數(shù)中的消息編號(hào)構(gòu)成一個(gè)分支結(jié)構(gòu),對(duì)于需要處理的消息分別處理。不感興趣的消息則交給DefWindowProc來(lái)處理。
要注意的是窗口過程中要注意保存ebx,edi,esi和ebp寄存器,高級(jí)語(yǔ)言中不用自己操心這一點(diǎn),匯編中就要注意了,Windows內(nèi)部將這4個(gè)寄存器當(dāng)指針使用,如果返回時(shí)改變了它們的值,程序會(huì)馬上崩潰。proc后面的uses偽操作在子程序進(jìn)入和退出時(shí)自動(dòng)按插上push和pop寄存器指令,來(lái)保護(hù)這些寄存器的值。其實(shí)不僅是在窗口過程中是這樣,所有由應(yīng)用程序提供給Windows的回調(diào)函數(shù)都必須遵循這個(gè)規(guī)定,如定時(shí)器回調(diào)函數(shù)等,所有Win32 API也遵循這個(gè)規(guī)定,所以調(diào)用API后,ebx,edi,esi和ebp寄存器的值總是不會(huì)被改變的,但ecx和edx的值就不一定了。
uMsg參數(shù)指定的消息有一定的范圍,Windows標(biāo)準(zhǔn)窗口中已經(jīng)預(yù)定義的值在0~03ffh之間(1024個(gè)),用戶可以自定義一些消息,通過SendMessage等函數(shù)傳給窗口過程做自定義的處理工作,這時(shí)可以使用的值是從0400h開始的,WM_USER就定義為00000400h,當(dāng)程序員定義多個(gè)用戶消息的時(shí)候,一般使用WM_USER+1,WM_USER+2,之類的定義方法。
wParam和lParam參數(shù)是消息所附帶的參數(shù),它隨消息的不同而不同,對(duì)于不同的消息,它們的含義必須分別從手冊(cè)中查明:如WM_MOUSEMOVE消息中,wParam是標(biāo)志,lParam是鼠標(biāo)位置;而在WM_GETTEXT消息中,wParam是要獲取的字符數(shù),lParam是緩沖地址;而對(duì)于WM_COPY消息來(lái)說(shuō),它不需要額外的信息,所以兩個(gè)參數(shù)都沒有定義。
處理了不同的消息,必須返回規(guī)定的值給Windows,返回值也需要分別從手冊(cè)中查明,比如處理WM_CREATE消息的時(shí)候,如果返回0表示成功;如果程序無(wú)法初始化,如申請(qǐng)內(nèi)存失敗,那么可以返回-1,Windows就不會(huì)繼續(xù)窗口的創(chuàng)建過程。一些消息的返回值則沒有定義,但大部分的消息處理以后都以返回0表示成功,所以程序中把默認(rèn)的返回語(yǔ)句放在最后,將eax清零后返回,如果在處理某個(gè)消息的時(shí)候需要返回不同的值,可以以分支中將eax賦值后直接用ret指令返回。對(duì)于DefWindowProc的返回值,我們不對(duì)它進(jìn)行干涉,所以直接將eax不做修改地用ret返回。
WM_CLOSE消息是按下了窗口右上角的“關(guān)閉”按鈕后收到的,程序可以在這里處理和關(guān)閉窗口相關(guān)的事情,一般是相關(guān)資源的釋放工作,如釋放內(nèi)存、保存工作和提示用戶是否保存工作等,如記事本程序在未保存的時(shí)候單擊“關(guān)閉”按鈕,會(huì)有提示框提示是否先保存文件,單擊“取消”按鈕的話,記事本不會(huì)關(guān)閉,這個(gè)步驟就是在WM_CLOSE消息處理中完成的。如果處理WM_CLOSE消息時(shí)直接返回,那么窗口不會(huì)關(guān)閉,因?yàn)檫@個(gè)消息只是Windows通知窗口用戶剛才單擊了“關(guān)閉”按鈕而已,窗口采用什么樣的行為是窗口的事。當(dāng)窗口決定關(guān)閉的時(shí)候,需要程序自己調(diào)用DestroyWindow來(lái)摧毀窗口,并用PostQuitMessage向消息循環(huán)發(fā)送WM_QUIT消息來(lái)退出消息循環(huán)。調(diào)用PostQuitMessage時(shí)的參數(shù)是退出碼,就是GetMessage收到的WM_QUIT后MSG結(jié)構(gòu)wParam字段中的東西,在這里使用NULL。
PostQuitMessage是初學(xué)者容易遺漏的函數(shù),如果沒有這條語(yǔ)句,外觀上窗口是從屏幕上消失了,但主程序中的消息循環(huán)卻沒有收到WM_QUIT,結(jié)果還在那里打轉(zhuǎn)。常有人調(diào)試的時(shí)候丟了這條語(yǔ)句,結(jié)果再一次編譯的時(shí)候就收到錯(cuò)誤:LINK fatal error LNK1104:cannot open file “xxx.exe”,表示exe文件現(xiàn)在不可寫。
Windows為什么不在窗口摧毀的時(shí)候自動(dòng)發(fā)送一個(gè)WM_QUIT消息,而必須由用戶程序自己通過PostQuitMessage函數(shù)發(fā)送呢?其實(shí)很好理解:因?yàn)槠聊簧峡赡懿恢挂粋€(gè)窗口,Windows無(wú)法確定哪個(gè)窗口的關(guān)閉代表著程序結(jié)束。試想一下,用戶打開了一個(gè)輸入?yún)?shù)的小窗口,單擊“確定”按鈕后關(guān)閉并回到主窗口,Windows卻不分三七二十一自動(dòng)發(fā)送了一個(gè)WM_QUIT,程序就會(huì)莫名其妙地退出了。
收到消息的順序
窗口過程收到消息是有一定順序的,收到第一條消息并不是從消息循環(huán)開始以后,而是在CreateWindowEx中就開始了,顯示和刷新窗口的函數(shù)ShowWindow和UpdateWindow也向窗口過程發(fā)送消息,這一點(diǎn)并不奇怪,因?yàn)?/span>Windows在CreateWindowEx前調(diào)用RegisterClassEx的時(shí)候就已經(jīng)得到窗口過程的地址了。并且在建立窗口的過程中需要窗口過程的配合。
下面分別列出調(diào)用CreateWindowEx和ShowWindow的時(shí)候窗口過程收到的消息。
調(diào)用CreateWindowEx時(shí)窗口過程收到的消息
|
消息發(fā)生 |
說(shuō)明 |
|
WM_GETMINMAXINFO |
獲取窗口大小,以便初始化 |
|
WM_NCCREATE |
非客戶區(qū)開始建立 |
|
WM_NCCALCSIZE |
計(jì)算客戶區(qū)大小 |
|
WM_CREATE |
窗口建立 |
調(diào)用ShowWindow時(shí)窗口過程收到的消息
|
消息發(fā)生 |
說(shuō)明 |
|
WM_SHOWWINDOW |
顯示窗口 |
|
WM_WINDOWPOSCHANGING |
窗口位置準(zhǔn)備改變 |
|
WM_ACTIVATEAPP |
窗口準(zhǔn)備激活 |
|
WM_NCACTIVATE |
激活狀態(tài)改變 |
|
WM_GETTEXT |
取窗口名稱(顯示標(biāo)題欄用) |
|
WM_ACTIVATE |
窗口準(zhǔn)備激活 |
|
WM_SETFOCUS |
窗口獲得焦點(diǎn) |
|
WM_NCPAINT |
需要繪畫窗口邊框 |
|
WM_ERASEBKGND |
需要擦除背景 |
|
WM_WINDOWPOSCHANGED |
窗口益已經(jīng)改變 |
|
WM_SIZE |
窗口大小已經(jīng)改變 |
|
WM_MOVE |
窗口位置已經(jīng)移動(dòng) |
然后程序執(zhí)行UpdateWindow,這個(gè)函數(shù)向窗口過程發(fā)送一條WM_PAINT消息,接著,主程序開始進(jìn)入消息循環(huán),Windows根據(jù)各種因素給窗口過程發(fā)送相應(yīng)的消息,一直到調(diào)用DestroyWindow為止。
調(diào)用DestroyWindow時(shí)窗口過程收到的消息
|
消息發(fā)生 |
說(shuō)明 |
|
WM_NCACTIVATE |
窗口激活狀態(tài)改變 |
|
WM_ACTIVATE |
窗口準(zhǔn)備非激活 |
|
WM_ACTIVATEAPP |
窗口準(zhǔn)備非激活 |
|
WM_KILLFOCUS |
失去焦點(diǎn) |
|
WM_DESTROY |
窗口即將被摧毀 |
|
WM_NCDESTROY |
窗口的非客戶區(qū)及所有子窗口已經(jīng)被摧毀 |
在所有這些階段的消息中,大部分的消息都不需要程序自己關(guān)心,Windows只是盡義務(wù)通知窗口過程而已,窗口過程轉(zhuǎn)手就交給DefWindowProc去處理了。程序需要關(guān)心的消息有下面這些,可以根據(jù)需要選擇使用:
WM_CREATE 放置窗口初始化代碼,如建立各種子窗口(狀態(tài)欄和工具欄等)。
WM_SIZE 放置位置安排的代碼,因?yàn)榻⒌淖哟翱诳赡苄枰S窗口大小的改變而移動(dòng)位置。
WM_PAINT 如果需要自己繪制客戶區(qū),則在這里安排代碼。
WM_CLOSE 向用戶確認(rèn)是否退出,如果退出則摧毀窗口并發(fā)送WM_QUIT消息。
WM_DESTROY 窗口摧毀,在這里放置釋放資源等掃尾代碼。
在例子程序中,我們處理了WM_PAINT消息來(lái)繪制客戶區(qū),功能就是在窗口中的中間寫上一行字:Win32 Assembly, Simple and powerful! 過程是先通過BeginPaint獲取窗口客戶區(qū)的“設(shè)備環(huán)境”句柄,然后通過GetClientRect獲取客戶區(qū)的大小,最后通過DrawText函數(shù)將字符串按照取得的屏幕大小居中寫到“設(shè)備環(huán)境”中,也就是窗口上。如果不需要顯示這個(gè)字符串,則連WM_PAINT消息也不用處理。
消息的默認(rèn)處理:DefWindowProc
Windows預(yù)定義的消息范圍是0~03ffh,共1024個(gè)消息,查看一下頭文件Windows.inc,可以發(fā)現(xiàn)實(shí)際已定義的消息數(shù)目有幾百個(gè),這些消息中的大部分對(duì)于窗口的運(yùn)行來(lái)說(shuō)都是必需的,如果窗口過程要處理每一種消息,那么窗口過程中的elseif語(yǔ)句就會(huì)綿延數(shù)千行,但是窗口的行為就是由處理這些消息的方法來(lái)表現(xiàn)的,不處理又不行,怎么辦呢?
實(shí)際上大部分窗口的行為都是差不多的,這意味著如果要窗口過程處理全部的消息,不同窗口的窗口過程代碼應(yīng)該是大同上異的,那么可以用一個(gè)模塊來(lái)以默認(rèn)的方式處理消息,Win32中的DefWindowProc函數(shù)實(shí)現(xiàn)的就是這個(gè)功能。
不要小看了這個(gè)DefWindowProc,正是它用默認(rèn)的方式處理了幾百種消息,才使用戶能用區(qū)區(qū)百來(lái)行代碼寫出一個(gè)全功能的窗口。也正是所有的窗口都用DefWindowProc默認(rèn)處理程序自己不處理的消息,才使它們的行為看上去大同小異,因?yàn)樗鼈儽澈髮?shí)際上是同一塊代碼在處理。
在窗口過程的分支語(yǔ)句中,用戶處理所有需要個(gè)性化處理的消息,對(duì)于表現(xiàn)行為是默認(rèn)行為的消息,則在else分支中用DefWindowProc來(lái)處理,對(duì)于Windows來(lái)說(shuō),它并不關(guān)心消息在窗口過程中是程序用自己的代碼處理的還是用DefWindowProc處理的,它只看eax中的返回值來(lái)了解處理結(jié)果,所以不管消息是誰(shuí)處理的,都必須在eax中返回正確的值。DefWindowProc返回時(shí)eax中就是它對(duì)消息的處理結(jié)果,程序只要直接把eax傳回給Windows就行了,所以在例子程序中,DefWindowProc后面直接用一句ret指令返回。
DefWindowProc中對(duì)一些消息的處理方法,如果和用戶期望的不同,就必須在窗口過程中自己處理。
DefWindowProc對(duì)一些消息的默認(rèn)處理方式
|
消息 |
DefWindowProc的處理方式 |
|
WM_PAINT |
發(fā)送WM_ERASEBKGND消息來(lái)擦除背景 |
|
WM_ERASEBKGND |
用窗口類結(jié)構(gòu)中的hbrBackground刷子來(lái)繪畫窗口背景 |
|
WM_CLOSE |
調(diào)用DestroyWindow來(lái)摧毀窗口 |
|
WM_NCLBUTTONDBLCLK |
這是非客戶區(qū)(如標(biāo)題欄)鼠標(biāo)雙擊消息,DefWindowProc測(cè)試鼠標(biāo)的位置,然后再采取相應(yīng)的措施,如標(biāo)題欄雙擊將最大化和恢復(fù)窗口 |
|
WM_NCLBUTTONUP |
這非客戶區(qū)鼠標(biāo)標(biāo)題釋放消息,同樣,DefWindowProc測(cè)試鼠標(biāo)的位置然后再采取相應(yīng)的措施,如鼠標(biāo)在“關(guān)閉”按鈕的位置釋放將導(dǎo)致發(fā)送WM_CLOSE消息 |
|
WM_NCPAINT |
非客戶區(qū)繪制消息,DefWindowProc將繪制邊框和客戶區(qū) |
從這些默認(rèn)的處理方法可以看出,想要一個(gè)窗口和別的窗口看起來(lái)不一樣,比如想要窗口看起來(lái)像蘋果機(jī)的窗口一樣,并且把關(guān)閉按鈕移到標(biāo)題欄最左邊去,那么可以自己處理WM_NCPAINT消息,把非客戶區(qū)畫成蘋果機(jī)窗口的樣子,并把關(guān)閉按鈕畫到標(biāo)題欄左邊去,并且自己處理WM_NCLBUTTONUP消息,當(dāng)檢測(cè)到鼠標(biāo)按下的位置在自己的關(guān)閉按鈕上的時(shí)候,則發(fā)送WM_CLOSE消息。對(duì)別的消息的處理思路也可以按這種方法類推。
另外,可以發(fā)現(xiàn)DefWindowProc對(duì)WM_CLOSE的默認(rèn)處理是調(diào)用DestroyWindow摧毀窗口,DestroyWindow會(huì)引發(fā)一個(gè)WM_DESTROY消息,WM_CLOSE和WM_DESTROY的不同之處是:WM_CLOSE代表用戶有關(guān)閉的意向,窗口過程有權(quán)不“服從”,但收到WM_DESTROY的時(shí)候窗口已經(jīng)在關(guān)閉過程中了,不管窗口過程愿不愿意,窗口的關(guān)閉已經(jīng)是不可挽回的事了。
對(duì)于這兩個(gè)消息,窗口過程必須處理其中的一個(gè),因?yàn)楸仨氂袀€(gè)地方發(fā)送WM_QUIT消息來(lái)結(jié)束消息循環(huán),例子程序中處理WM_CLOSE消息,在其中用DestroyWindow摧毀窗口,再調(diào)用PostQuitMessage結(jié)束消息循環(huán);程序也可以不處理WM_CLOSE消息,讓DefWindowProc以默認(rèn)處理的方式摧毀窗口,但這時(shí)候必須處理WM_DESTROY消息,在其中調(diào)用PostQuitMessage發(fā)送WM_QUIT以結(jié)束消息循環(huán)。
posted on 2010-08-17 11:49 luqingfei 閱讀(3029) 評(píng)論(0) 編輯 收藏 引用 所屬分類: Win32匯編程語(yǔ)言序設(shè)計(jì)

