?? 附錄a-c.txt
字號:
szUpdateWindow2 db 'UpdateWindow end',0dh,0
szGetMsg1 db 'Getting Message...',0dh,0
szGetMsg2 db '[%04x]Message gotten',0dh,0
szDispatchMsg1 db 'Dispatching Message...',0dh,0
szDispatchMsg2 db 'DispatchMessage end',0dh,0
.
invoke _SendtoNotepad,addr szCreateWindow1
invoke CreateWindowEx,.
invoke _SendtoNotepad,addr szCreateWindow2
invoke _SendtoNotepad,addr szShowWindow1
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke _SendtoNotepad,addr szShowWindow2
invoke _SendtoNotepad,addr szUpdateWindow1
invoke UpdateWindow,hWinMain
invoke _SendtoNotepad,addr szUpdateWindow2
.
上面代碼中的粗體部分就是相對于FirstWindow 程序增加的內(nèi)容,好了,現(xiàn)在DOS 控制
臺上鍵入nmake 將MsgWindow 程序編譯出來,然后打開記事本,再運(yùn)行MsgWindow.exe,
如果記事本上出現(xiàn)一大堆的東西,就說明實(shí)驗(yàn)可以開始了!
B.2 開始實(shí)驗(yàn)
實(shí)驗(yàn)1. 驗(yàn)證收到消息的順序
打開記事本,然后運(yùn)行MsgWindow 程序,記事本上出現(xiàn)的內(nèi)容如下:
Creating Window...
WndProc: [0024]WM_GETMINMAXINFO 00000000 0012fda4
WndProc: [0081]WM_NCCREATE 00000000 0012fd8c
WndProc: [0083]WM_NCCALCSIZE 00000000 0012fdc4
WndProc: [0001]WM_CREATE 00000000 0012fd68
CreateWindow end
Showing Window...
WndProc: [0018]WM_SHOWWINDOW 00000001 00000000
WndProc: [0046]WM_WINDOWPOSCHANGING 00000000 0012fec0
WndProc: [0046]WM_WINDOWPOSCHANGING 00000000 0012fec0
WndProc: [001c]WM_ACTIVATEAPP 00000001 00000450
WndProc: [0086]WM_NCACTIVATE 00000001 00000000
WndProc: [000d]WM_GETTEXT 000001fe 0012f52c
WndProc: [0006]WM_ACTIVATE 00000001 00000000
WndProc: [0007]WM_SETFOCUS 00000000 00000000
WndProc: [0085]WM_NCPAINT 00000001 00000000
WndProc: [000d]WM_GETTEXT 000001fe 0012f52c
WndProc: [0014]WM_ERASEBKGND e3010449 00000000
WndProc: [0047]WM_WINDOWPOSCHANGED 00000000 0012fec0
WndProc: [0005]WM_SIZE 00000000 00450064
WndProc: [0003]WM_MOVE 00000000 004b0038
ShowWindow end
Updating Window...
WndProc: [000f]WM_PAINT 00000000 00000000
UpdateWindow end
Getting Message...
以WndProc 帶頭的是在窗口過程中收到的消息,顯然,和4.2.4 節(jié)中講述的是一致的,
在調(diào)用CreateWindowEx 時,窗口過程就開始接收消息,里面有重要的WM_CREATE ,然后
在ShowWindow 的時候,Windows 向窗口過程發(fā)送了很多的消息,而UpdateWindow 只給窗
口過程發(fā)送了一條WM_PAINT 消息,接下來就進(jìn)入了消息循環(huán)。
可以看到,GetMessage 函數(shù)是程序主動上交空閑時間的辦法之一,因?yàn)轱@示出Getting
Message..以后,程序就等著那里了,這表示程序的空閑時間并不浪費(fèi)在消息循環(huán)中,而是
在GetMessage 函數(shù)內(nèi)部由Windows 自己分配了。
接下來把鼠標(biāo)移過MsgWindow 窗口,在記事本上看到了什么?用戶一個小小的動作就
夠窗口過程忙的——我們看到了多次重復(fù)的下列內(nèi)容:
WndProc: [0084]WM_NCHITTEST 00000000 00830096
WndProc: [0020]WM_SETCURSOR 001b0304 02000001
[0200]Message gotten
Dispatching Message...
WndProc: [0200]WM_MOUSEMOVE 00000000 0038005e
DispatchMessage end
Getting Message...
首先,Windows 在GetMessage 沒有返回的時候就調(diào)用了兩次窗口過程,分別是處理
WM_NCHITTEST 和WM_SETCURSOR ,它們并不經(jīng)過消息循環(huán);然后,GetMessage 取到
[0200] 消息并返回,0200 是WM_MOUSEMOVE 消息的編號;接下來,DispatchMessage 函
數(shù)開始工作,在這個函數(shù)的內(nèi)部,消息被Windows 發(fā)送給窗口過程處理,最后DispatchMessage
返回,然后開始新的GetMessage。
最后在MsgWindow 上單擊“關(guān)閉”按鈕,看發(fā)生了什么:
[00a1]Message gotten
Dispatching Message...
WndProc: [00a1]WM_NCLBUTTONDOWN 00000014 003d0097
WndProc: [0215]WM_CAPTURECHANGED 00000000 00000000
WndProc: [0112]WM_SYSCOMMAND 0000f060 003d0097
WndProc: [0010]WM_CLOSE 00000000 00000000
WndProc: [0046]WM_WINDOWPOSCHANGING 00000000 0012fad8
WndProc: [0047]WM_WINDOWPOSCHANGED 00000000 0012fad8
WndProc: [0086]WM_NCACTIVATE 00000000 00000000
WndProc: [0006]WM_ACTIVATE 00000000 00000000
WndProc: [001c]WM_ACTIVATEAPP 00000000 00000450
WndProc: [0008]WM_KILLFOCUS 00000000 00000000
WndProc: [0002]WM_DESTROY 00000000 00000000
WndProc: [0082]WM_NCDESTROY 00000000 00000000
DispatchMessage end
Getting Message...
[0012]Message gotten
GetMessage 收到的是按下鼠標(biāo)的WM_NCLBUTTONDOWN 的消息,由DispatchMessage
轉(zhuǎn)給窗口過程處理后,窗口過程將它轉(zhuǎn)手給了DefWindowProc,DefWindowProc 根據(jù)鼠標(biāo)的
位置得出結(jié)論:用戶按的是“關(guān)閉”按鈕,放開鼠標(biāo)后,它就給窗口過程發(fā)送WM_CLOSE
消息,當(dāng)窗口過程調(diào)用DestroyWindow 后,窗口被摧毀,窗口過程最后收到的是
WM_DESTROY 消息和WM_NCDESTROY 消息,而消息循環(huán)中GetMessage 最后收到的是
0012 號WM_QUIT 消息,消息循環(huán)結(jié)束。
實(shí)驗(yàn)2. 全部消息都經(jīng)過消息循環(huán)嗎
在做這個實(shí)驗(yàn)之前,讀者已經(jīng)知道并不是所有的消息都是經(jīng)過消息循環(huán)的,它們中有
些是Windows 直接發(fā)送到窗口過程的,上一個實(shí)驗(yàn)中就已經(jīng)可以看到GetMessage 返回的
次數(shù)明顯比調(diào)用窗口過程的次數(shù)少,這意味著窗口過程有很多次是由Windows 直接調(diào)用
的。
這次,我們用極端的方式來驗(yàn)證,先把消息循環(huán)中的DispatchMessage 去掉,這樣
GetMessage 得到的消息將不會再被送到窗口過程了,窗口過程收到的就是由Windows 直接調(diào)
用的。改變后的源代碼見所附光盤中的Appendix B\MsgWindow02 目錄。
編譯后,同樣先打開記事本,再執(zhí)行MsgWindow ,然后將鼠標(biāo)移過MsgWindow 窗口,
并嘗試著單擊“關(guān)閉”按鈕和雙擊等各種動作,結(jié)果是窗口過程還是在被調(diào)用,如下所示:
WndProc: [0084]WM_NCHITTEST 00000000 007e0088
WndProc: [0020]WM_SETCURSOR 0026030c 02000001
WndProc: [0084]WM_NCHITTEST 00000000 006c0070
WndProc: [0020]WM_SETCURSOR 0026030c 02000001
.
由于沒有了DispatchMessage ,大部分消息被忽略了,窗口就停在了屏幕上,不能進(jìn)行移
動、縮放或關(guān)閉等操作,但還是有一部分消息直接由Windows 發(fā)送給窗口過程,它們是鼠標(biāo)
位置測試的WM_NCHITTEST 消息和要求設(shè)置光標(biāo)的WM_SETCURSOR 消息,所以在鼠標(biāo)
移動到邊框的時候,鼠標(biāo)光標(biāo)還是會變成雙箭頭的樣子。
另外,嘗試著單擊其他窗口來切換焦點(diǎn),然后再單擊標(biāo)題欄來重新激活窗口,可以發(fā)
現(xiàn)WM_MOUSEACTIVATE,WM_ACTIVATE 和WM_KILLFOCUS 等消息也是不經(jīng)過消
息循環(huán)的。接下來,把一個窗口移動到MsgWindow 窗口前覆蓋它的位置,再移開,可以
發(fā)現(xiàn)WM_SYNCPAINT 和WM_ERASEBKGND 等消息也是由Windows 直接發(fā)給窗口過
程的。
最后,關(guān)閉窗口,當(dāng)然這個窗口只能用Ctrl+Alt+Del 組合鍵在任務(wù)管理器中關(guān)閉了!
實(shí)驗(yàn)3. TranslateMessage 有什么用
首先執(zhí)行實(shí)驗(yàn)1 的MsgWindow ,在窗口上敲幾個鍵,每次敲一個鍵,得到的消息是:
WM_KEYDOWN,WM_CHAR 和WM_KEYUP 。如果按下鍵盤不放,則首先得到一個
WM_KEYDOWN,接下來就是重復(fù)的WM_CHAR 和WM_KEYUP 消息,直到放開鍵盤為止,
最后才會看到一個WM_KEYUP。顯示如下:
WndProc: [0100]WM_KEYDOWN 00000041 001e0001
WndProc: [0102]WM_CHAR 00000061 001e0001
WndProc: [0101]WM_KEYUP 00000041 c01e0001
在WM_KEYDOWN 和WM_KEYUP 消息中,wParam 中是按鍵的掃描碼,上面的數(shù)據(jù)
是按下了“A”鍵得到的,00000041h 是“A”的掃描碼,到了WM_CHAR 消息中,wParam
中就是已經(jīng)轉(zhuǎn)換過的ASCII 碼61 了,代表輸入的是小寫的字母“a”。
好!現(xiàn)在從程序中去掉TranslateMessage 語句(修改以后的源代碼放在Appendix
B\MsgWindow03 目錄中),然后看這個程序的運(yùn)行結(jié)果,同樣,按幾次鍵以及按下鍵盤不放,
我們發(fā)現(xiàn):這中間的區(qū)別就是少了WM_CHAR,所以只有在處理鍵盤輸入要用到轉(zhuǎn)換后的
ASCII 碼的時候,TranslateMessage 函數(shù)才是有用的,在別的時候完全可以省略這個語句。這
個函數(shù)的功能就是看到WM_KEYDOWN 的時候把消息檢查一下,然后根據(jù)鍵值將一條新的
WM_CHAR 或WM_SYSCHAR 消息放入消息循環(huán)中。
實(shí)驗(yàn)4. DefWindowProc 做了什么工作
現(xiàn)在把DefWindowProc 語句去掉(源代碼詳見Appendix B\MsgWindow04 目錄),然后
再以同樣的方法運(yùn)行,窗口根本就沒有出現(xiàn)!看記事本中出現(xiàn)了什么:
Creating Window...
WndProc: [0024]WM_GETMINMAXINFO 00000000 0012fda4
WndProc: [0081]WM_NCCREATE 00000000 0012fd8c
WndProc: [0082]WM_NCDESTROY 00000000 00000000
CreateWindow end
Showing Window...
ShowWindow end
Updating Window...
UpdateWindow end
Getting Message...
原來在建立窗口的時候執(zhí)行到WM_NCCREATE 消息后窗口就摧毀掉了,看
WM_NCCREATE 的說明:The DefWindowProc function returns TRUE ,原來需要返回1 來表
示執(zhí)行成功,所以需要處理WM_NCCREATE 并返回1,現(xiàn)在在窗口過程中加上下列分支:
.elseif eax == WM_NCCREATE
mov eax,1
ret
接著編譯后執(zhí)行,怎么編譯不成功了?不能寫exe 文件?原來上次的程序還停留在消息循
環(huán)中沒有退出來,讓我們在任務(wù)管理器中將它終止再編譯,成功了!
好!現(xiàn)在繼續(xù)執(zhí)行,窗口成功建立了,但似乎陷入了死循環(huán),因?yàn)橛浭卤旧喜煌5赜邢?息冒出來,而且只是冒出WM_PAINT 消息來,為什么呢?原來WM_PAINT 消息是不能不處
理的,也不能丟棄,只要Windows 認(rèn)為窗口的客戶區(qū)需要繪畫(或者說是無效的),它就會
不停地向窗口發(fā)送WM_PAINT 消息,一般WM_PAINT 消息的處理中用BeginPaint 和EndPaint
會隱含地讓客戶區(qū)有效,如果不用BeginPaint/EndPaint ,程序必須顯式地把客戶區(qū)設(shè)置為有
效,Windows 才不會再發(fā)送WM_PAINT 消息。這個函數(shù)是ValidateRect ,現(xiàn)在在分支中再加
上處理WM_PAINT 的代碼:
.elseif eax == WM_PAINT
invoke ValidateRect,hWnd,NULL
再編譯執(zhí)行,現(xiàn)在程序可以正常執(zhí)行下去了,記事本上出現(xiàn)的信息也顯示程序停留在了
GetMessage 處,一切正常。但是,窗口在哪里呢,屏幕上什么都沒有,隱身了?把鼠標(biāo)移到
窗口原來應(yīng)該出現(xiàn)的地方,記事本中熟悉的WM_NCHITTEST 和WM_SETCURSOR 消息出
現(xiàn)了,原來窗口還在那里,只不過沒有了DefWindowProc 的處理,窗口的繪畫等所有工作都
沒有做,窗口的邊框與客戶區(qū)等所有東西連畫都沒有畫上去,所以窗口是存在的,但我們看
不到它!
是不是再加上WM_NCPAINT 消息自己畫邊框呢,這就不是這個實(shí)驗(yàn)的內(nèi)容了。我們已
經(jīng)知道,DefWindowProc 做的工作太多了,缺了它我們要補(bǔ)上的代碼可不是一兩個分支的問
題,而是上百個分支了!在這個實(shí)驗(yàn)中,我們根本不可能把它補(bǔ)全。
附錄C
瀏覽目錄對話框
C.1 瀏覽目錄對話框簡介
在眾多由系統(tǒng)提供的對話框中,除了第8 章中介紹的眾多通用對話框外,還有一個很常
用的瀏覽目錄對話框,該對話框如圖C.1 所示,這個對話框雖然也是通用型的,但是它是由
Shell32.dll 提供的,而不是由Comdlg32.dll 提供的,在實(shí)現(xiàn)的方法上也和上面介紹的通用對
話框有很大的不同,本節(jié)以一個例子來演示它的使用。
圖C.1 瀏覽目錄對話框
例子程序的源代碼位于所附光盤的Appendix C\BrowseFolder 目錄中,目錄中包含了
BrowseFolder.asm 文件和_BrowseFolder.asm 文件。BrowseFolder.asm 文件的內(nèi)容很簡單,如
下所示:
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定義
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include shell32.inc
includelib shell32.lib
include ole32.inc
includelib ole32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 數(shù)據(jù)段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
szPath db MAX_PATH dup (?)
.data
szSelect db '您選擇的目錄',0
szNoSelect db '您按下了取消鍵',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
include _BrowseFolder.asm
start:
invoke GetCurrentDirectory,sizeof szPath,addr szPath
invoke _BrowseFolder,NULL,addr szPath
.if eax
invoke MessageBox,NULL,offset szPath,\
offset szSelect,MB_OK
.else
invoke MessageBox,NULL,offset szNoSelect,NULL,MB_OK
.endif
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
主文件中僅包含了幾句調(diào)用和顯示結(jié)果的代碼,全部的功能集中在_BrowseFolder.asm
中,該文件用include 語句包含進(jìn)主文件中,這樣安排代碼的原因是目錄瀏覽對話框的實(shí)現(xiàn)比
較復(fù)雜,把功能模塊寫成一個單獨(dú)的文件可以便于在其他文件中引用,讀者也可以直接把這
個源文件不加修改地用在其他地方。_BrowseFolder.asm 文件的內(nèi)容如下:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; IUnknown 接口定義
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -