分析窗口程序
了解了消息驅(qū)動(dòng)體系的工作流程以后,讓我們來(lái)分析如何用Win32匯編實(shí)現(xiàn)這一切。
模塊和句柄
模塊的概念
一個(gè)模塊代表的是一個(gè)運(yùn)行中的exe文件或dll文件,用來(lái)代表這個(gè)文件中所有的代碼和資源,磁盤(pán)上的文件不是模塊,裝入內(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ù)中都要用到程序的模塊句柄,以便利用程序中的各種資源,所以在程序中一開(kāi)始就先取得模塊句柄并存放到一個(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í)例的問(wèn)題,實(shí)際上在Win32中,實(shí)例句柄就是模塊句柄,但很多API原型中用到模塊句柄的時(shí)候使用的名稱還是沿用hInstance,所以我們還是把變量名稱取為hInstance。
在C++語(yǔ)言的編程中,hInstance通過(guò)WinMain由系統(tǒng)傳入,WinMain的原型是:
WinMain(hInstance, hPrevInstance, lpszCmdParam, nCmdShow)
程序不用自己去獲得hInstance,但在Win32匯編中必須自己獲取,如果不了解hModule就是hInstance的話,就無(wú)法得知如何得到hInstance,因?yàn)椴](méi)有一個(gè)類似于GetInstanceHandle之類的API函數(shù)。
句柄是什么
隨著分析的深入,句柄(handle)一詞也出現(xiàn)得頻繁了起來(lái),“句柄”是什么呢?句柄只是一個(gè)數(shù)值而已,它的值對(duì)程序來(lái)說(shuō)是沒(méi)有意義的,它只是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ū)包時(shí)營(yíng)業(yè)員給的紙條,紙條上的標(biāo)記用戶并不知道是什么意思,但把它交還給營(yíng)業(yè)員的時(shí)候,她自然會(huì)找到正確的書(shū)包。
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ù)傳遞過(guò)去的,而是定義在一個(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 ? ;窗口過(guò)程的地址
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將它填為全零,再填寫(xiě)結(jié)構(gòu)的各個(gè)字段,這樣,沒(méi)有賦值的部分就保持為0,結(jié)構(gòu)各字段的含義如下:
hIcon 圖標(biāo)句柄,指定顯示在窗口標(biāo)題欄左上角的圖標(biāo)。Windows已經(jīng)預(yù)定課外作業(yè)了一些圖標(biāo),同樣,程序也可以使用在資源文件中定義的圖標(biāo),這些圖標(biāo)的句柄可以用LoadIcon函數(shù)獲得。例子程序沒(méi)有用到圖標(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è)地方都沒(méi)有指定,那么建立的窗口上就沒(méi)有菜單。
hInstance 指定要注冊(cè)的窗口類屬于哪個(gè)模塊,模塊句柄在程序開(kāi)始的地方已經(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í)是否重畫(huà)窗口。比較重要的是CS_DBLCLKS風(fēng)格,指定了它,Windows才會(huì)把在窗口中快速兩次單擊鼠標(biāo)的行為翻譯為雙擊消息WM_LBUTTONDBLCLK發(fā)給窗口過(guò)程。
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è)類建立的窗口的窗口過(guò)程地址。通過(guò)這個(gè)參數(shù),Windows就知道了在DispatchMessage函數(shù)中把窗口消息發(fā)到哪里去,一個(gè)窗口過(guò)程可以為多個(gè)窗口服務(wù),只要這些窗口是基于同一個(gè)窗口類建立的。Windows中不同應(yīng)用程序中的按鈕和文本框的行為都是一樣的,就是因?yàn)樗鼈兪腔谙嗤?/span>Windows預(yù)定義類建立的,它們背后的窗口過(guò)程其實(shí)是同一段代碼。
結(jié)構(gòu)中的style表示窗口的風(fēng)格,Windows已經(jīng)有一些預(yù)定義的值,它們是以CS_(Class Style的縮寫(xiě))開(kāi)始的標(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也并沒(méi)有什么不同,但強(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)中沒(méi)有定義的外觀、標(biāo)題、位置、大小和邊框類型等屬性,這些屬性是在建立窗口時(shí)才指定的。
和注冊(cè)窗口類時(shí)用一個(gè)結(jié)構(gòu)傳遞所有參數(shù)不同,建立窗口時(shí)所有的屬性都是用單個(gè)參數(shù)的方式傳遞的,建立窗口的函數(shù)是CreateWindowEx(注意不要寫(xiě)成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”中指定的窗口過(guò)程中去,當(dāng)然,這里也可以是Window預(yù)定義的類名。
lpWindowName 指向表示窗口名稱的字符串,該名稱會(huì)顯示在標(biāo)題欄上。如果該參數(shù)空白,則標(biāo)題欄上什么都沒(méi)有。
hMenu 窗口上要出現(xiàn)的菜單的句柄。在注冊(cè)窗口類的時(shí)候也定義了一個(gè)菜單,那是窗口的默認(rèn)菜單,意思是如果這里沒(méi)有定義菜單(用參數(shù)NULL)而注冊(cè)窗口類時(shí)定義了菜單,則使用窗口類中定義的菜單;如果這里指定了菜單句柄,則不管窗口類中有沒(méi)有定義都將使用這里定義的菜單;兩個(gè)地方都沒(méi)有定義菜單句柄,則窗口上沒(méi)有菜單。另外,當(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開(kāi)始就有的屬性,下表列出了一些常見(jiàn)的dwStyle定義,它們是一些以WS(Windows Style的縮寫(xiě))為開(kāi)頭的預(yù)定義值。
預(yù)定義值
|
16進(jìn)制值
|
含義
|
WS_OVERLAPPED
|
00000000h
|
普通的重疊式窗口
|
WS_POPUP
|
80000000h
|
彈出式窗口(沒(méi)有標(biāo)題欄)
|
WS_CHILD
|
40000000h
|
子窗口
|
WS_MINIMIZE
|
20000000h
|
初始狀態(tài)是最小化的
|
WS_VISIBLE
|
10000000h
|
初始狀態(tài)是可見(jiàn)的
|
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_開(kāi)頭的預(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í)候,窗口雖已建立,但還沒(méi)有在屏幕上顯示出來(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”,然后在窗口過(guò)程的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_開(kāi)頭的預(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ì)列里取得消息,填寫(xiě)好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)行一些鍵盤(pán)消息的轉(zhuǎn)換,當(dāng)有鍵盤(pán)按下和放開(kāi)時(shí),Windows產(chǎn)生WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息,但這些消息的參數(shù)中包含的是按鍵的掃描碼,轉(zhuǎn)換成常用的ASCII碼要經(jīng)過(guò)查表,很不方便,TranslateMessage遇到鍵盤(pán)消息則將掃描碼轉(zhuǎn)換成ASCII碼并在消息隊(duì)列中插入WM_CHAR或WM_SYSCHAR消息,參數(shù)就是轉(zhuǎn)換好的ASCII碼,如此一來(lái),要處理鍵盤(pán)消息的話只要處理WM_CHAR消息就好了。遇到別的消息則TranslateMessage不做處理。
最后,由DispatchMessage將消息發(fā)送到窗口對(duì)應(yīng)的窗口過(guò)程去處理。窗口過(guò)程返回后DispatchMessage函數(shù)才返回,然后開(kāi)始新一輪消息循環(huán)。
其它形式的消息循環(huán):
GetMessage函數(shù)是程序空閑的時(shí)候主動(dòng)將控制權(quán)交還給Windows的一種方式,Windows是一個(gè)搶占式的多任務(wù)系統(tǒng),任務(wù)之間每20ms切換一次,試想一下,如果窗口程序在主窗口中采用死循環(huán)等待,消息由Windows直接發(fā)送到窗口過(guò)程,那么程序會(huì)是下列這種樣子:
invoke CreateWindow,
invoke ShowWindow,
invoke UpdateWindow,
.while dwQuitFlag == 0 ;要退出時(shí)在窗口過(guò)程中設(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ì)列里還沒(méi)有消息,那么程序還是停留在GetMessage內(nèi)部,這時(shí)就可以由Windows當(dāng)家作主沒(méi)收這20ms的時(shí)間片,如此保證了CPU資源的合理應(yīng)用。
如果應(yīng)用程序想把所有時(shí)間充分用回來(lái),消息隊(duì)列里沒(méi)有消息的時(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中返回非零值,沒(méi)有消息的時(shí)候它會(huì)直接返回,并在eax中返回零。所以在返回非零值的時(shí)候,程序檢查消息是否是WM_QUIT,是則結(jié)束消息循環(huán),不是則用標(biāo)準(zhǔn)流程處理消息;返回零的時(shí)候,表示是空閑時(shí)間,程序就可以做其他工作了,但插入做其他工作的代碼執(zhí)行時(shí)間不能過(guò)長(zhǎng),以不越過(guò)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。
窗口過(guò)程
窗口過(guò)程是給Windows回調(diào)用的,它必須遵循規(guī)定的格式。對(duì)窗口過(guò)程的子程序名并沒(méi)有規(guī)定,對(duì)Windows來(lái)說(shuō),窗口過(guò)程的地址才是唯一需要的,例子程序中的子程序名是_ProcWinMain,讀者可以改用任何名稱。窗口過(guò)程子程序的參數(shù)格式為:
WindowProc proc hwnd, uMsg, wParam, lParam
第一個(gè)參數(shù)是窗口句柄,一個(gè)窗口過(guò)程可能為多個(gè)基于同一個(gè)窗口類的窗口服務(wù),所以Windows回調(diào)的時(shí)候必須指出要操作的窗口,否則窗口過(guò)程不知道要去處理哪個(gè)窗口,FirstWindow程序只建立了一個(gè)窗口,所以每次傳遞過(guò)來(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è)字段是一樣的。
窗口過(guò)程的結(jié)構(gòu)
窗口過(guò)程一般有如下結(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
該過(guò)程主要是對(duì)uMsg參數(shù)中的消息編號(hào)構(gòu)成一個(gè)分支結(jié)構(gòu),對(duì)于需要處理的消息分別處理。不感興趣的消息則交給DefWindowProc來(lái)處理。
要注意的是窗口過(guò)程中要注意保存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í)不僅是在窗口過(guò)程中是這樣,所有由應(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è)),用戶可以自定義一些消息,通過(guò)SendMessage等函數(shù)傳給窗口過(guò)程做自定義的處理工作,這時(shí)可以使用的值是從0400h開(kāi)始的,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ù)都沒(méi)有定義。
處理了不同的消息,必須返回規(guī)定的值給Windows,返回值也需要分別從手冊(cè)中查明,比如處理WM_CREATE消息的時(shí)候,如果返回0表示成功;如果程序無(wú)法初始化,如申請(qǐng)內(nèi)存失敗,那么可以返回-1,Windows就不會(huì)繼續(xù)窗口的創(chuàng)建過(guò)程。一些消息的返回值則沒(méi)有定義,但大部分的消息處理以后都以返回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ù),如果沒(méi)有這條語(yǔ)句,外觀上窗口是從屏幕上消失了,但主程序中的消息循環(huán)卻沒(méi)有收到WM_QUIT,結(jié)果還在那里打轉(zhuǎn)。常有人調(diào)試的時(shí)候丟了這條語(yǔ)句,結(jié)果再一次編譯的時(shí)候就收到錯(cuò)誤:LINK fatal error LNK1104:cannot open file “xxx.exe”,表示exe文件現(xiàn)在不可寫(xiě)。
Windows為什么不在窗口摧毀的時(shí)候自動(dòng)發(fā)送一個(gè)WM_QUIT消息,而必須由用戶程序自己通過(guò)PostQuitMessage函數(shù)發(fā)送呢?其實(shí)很好理解:因?yàn)槠聊簧峡赡懿恢挂粋€(gè)窗口,Windows無(wú)法確定哪個(gè)窗口的關(guān)閉代表著程序結(jié)束。試想一下,用戶打開(kāi)了一個(gè)輸入?yún)?shù)的小窗口,單擊“確定”按鈕后關(guān)閉并回到主窗口,Windows卻不分三七二十一自動(dòng)發(fā)送了一個(gè)WM_QUIT,程序就會(huì)莫名其妙地退出了。
收到消息的順序
窗口過(guò)程收到消息是有一定順序的,收到第一條消息并不是從消息循環(huán)開(kāi)始以后,而是在CreateWindowEx中就開(kāi)始了,顯示和刷新窗口的函數(shù)ShowWindow和UpdateWindow也向窗口過(guò)程發(fā)送消息,這一點(diǎn)并不奇怪,因?yàn)?/span>Windows在CreateWindowEx前調(diào)用RegisterClassEx的時(shí)候就已經(jīng)得到窗口過(guò)程的地址了。并且在建立窗口的過(guò)程中需要窗口過(guò)程的配合。
下面分別列出調(diào)用CreateWindowEx和ShowWindow的時(shí)候窗口過(guò)程收到的消息。
調(diào)用CreateWindowEx時(shí)窗口過(guò)程收到的消息
消息發(fā)生
|
說(shuō)明
|
WM_GETMINMAXINFO
|
獲取窗口大小,以便初始化
|
WM_NCCREATE
|
非客戶區(qū)開(kāi)始建立
|
WM_NCCALCSIZE
|
計(jì)算客戶區(qū)大小
|
WM_CREATE
|
窗口建立
|
調(diào)用ShowWindow時(shí)窗口過(guò)程收到的消息
消息發(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
|
需要繪畫(huà)窗口邊框
|
WM_ERASEBKGND
|
需要擦除背景
|
WM_WINDOWPOSCHANGED
|
窗口益已經(jīng)改變
|
WM_SIZE
|
窗口大小已經(jīng)改變
|
WM_MOVE
|
窗口位置已經(jīng)移動(dòng)
|
然后程序執(zhí)行UpdateWindow,這個(gè)函數(shù)向窗口過(guò)程發(fā)送一條WM_PAINT消息,接著,主程序開(kāi)始進(jìn)入消息循環(huán),Windows根據(jù)各種因素給窗口過(guò)程發(fā)送相應(yīng)的消息,一直到調(diào)用DestroyWindow為止。
調(diào)用DestroyWindow時(shí)窗口過(guò)程收到的消息
消息發(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ù)通知窗口過(guò)程而已,窗口過(guò)程轉(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ū),功能就是在窗口中的中間寫(xiě)上一行字:Win32 Assembly, Simple and powerful! 過(guò)程是先通過(guò)BeginPaint獲取窗口客戶區(qū)的“設(shè)備環(huán)境”句柄,然后通過(guò)GetClientRect獲取客戶區(qū)的大小,最后通過(guò)DrawText函數(shù)將字符串按照取得的屏幕大小居中寫(xiě)到“設(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ō)都是必需的,如果窗口過(guò)程要處理每一種消息,那么窗口過(guò)程中的elseif語(yǔ)句就會(huì)綿延數(shù)千行,但是窗口的行為就是由處理這些消息的方法來(lái)表現(xiàn)的,不處理又不行,怎么辦呢?
實(shí)際上大部分窗口的行為都是差不多的,這意味著如果要窗口過(guò)程處理全部的消息,不同窗口的窗口過(guò)程代碼應(yīng)該是大同上異的,那么可以用一個(gè)模塊來(lái)以默認(rèn)的方式處理消息,Win32中的DefWindowProc函數(shù)實(shí)現(xiàn)的就是這個(gè)功能。
不要小看了這個(gè)DefWindowProc,正是它用默認(rèn)的方式處理了幾百種消息,才使用戶能用區(qū)區(qū)百來(lái)行代碼寫(xiě)出一個(gè)全功能的窗口。也正是所有的窗口都用DefWindowProc默認(rèn)處理程序自己不處理的消息,才使它們的行為看上去大同小異,因?yàn)樗鼈儽澈髮?shí)際上是同一塊代碼在處理。
在窗口過(guò)程的分支語(yǔ)句中,用戶處理所有需要個(gè)性化處理的消息,對(duì)于表現(xiàn)行為是默認(rèn)行為的消息,則在else分支中用DefWindowProc來(lái)處理,對(duì)于Windows來(lái)說(shuō),它并不關(guān)心消息在窗口過(guò)程中是程序用自己的代碼處理的還是用DefWindowProc處理的,它只看eax中的返回值來(lái)了解處理結(jié)果,所以不管消息是誰(shuí)處理的,都必須在eax中返回正確的值。DefWindowProc返回時(shí)eax中就是它對(duì)消息的處理結(jié)果,程序只要直接把eax傳回給Windows就行了,所以在例子程序中,DefWindowProc后面直接用一句ret指令返回。
DefWindowProc中對(duì)一些消息的處理方法,如果和用戶期望的不同,就必須在窗口過(guò)程中自己處理。
DefWindowProc對(duì)一些消息的默認(rèn)處理方式
消息
|
DefWindowProc的處理方式
|
WM_PAINT
|
發(fā)送WM_ERASEBKGND消息來(lái)擦除背景
|
WM_ERASEBKGND
|
用窗口類結(jié)構(gòu)中的hbrBackground刷子來(lái)繪畫(huà)窗口背景
|
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)像蘋(píng)果機(jī)的窗口一樣,并且把關(guān)閉按鈕移到標(biāo)題欄最左邊去,那么可以自己處理WM_NCPAINT消息,把非客戶區(qū)畫(huà)成蘋(píng)果機(jī)窗口的樣子,并把關(guān)閉按鈕畫(huà)到標(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)閉的意向,窗口過(guò)程有權(quán)不“服從”,但收到WM_DESTROY的時(shí)候窗口已經(jīng)在關(guān)閉過(guò)程中了,不管窗口過(guò)程愿不愿意,窗口的關(guān)閉已經(jīng)是不可挽回的事了。
對(duì)于這兩個(gè)消息,窗口過(guò)程必須處理其中的一個(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)。