?? 見招拆招《windows程序設計》(二) .txt
字號:
表3-4
前綴 數據型態
c char或WCHAR或TCHAR
by BYTE (無正負號字符)
n short
i int
x, y int分別用作x坐標和y坐標
cx, cy int分別用作x長度和y長度;C代表「計數器」
b或f BOOL (int);f代表「旗標」
w WORD (無正負號短整數)
l LONG (長整數)
dw DWORD (無正負號長整數)
fn function(函數)
s string(字符串)
sz 以字節值0結尾的字符串
h 句柄
p 指標
注冊窗口類別
窗口依照某一窗口類別建立,窗口類別用以標識處理窗口消息的窗口消息處理程序。
不同窗口可以依照同一種窗口類別建立。例如,Windows中的所有按鈕窗口-包括按鍵、復選框,以及單選按鈕-都是依據同一種窗口類別建立的。窗口類別定義了窗口消息處理程序和依據此類別建立的窗口的其它特征。在建立窗口時,要定義一些該窗口所獨有的特征。
在為程序建立窗口之前,必須首先呼叫RegisterClass注冊一個窗口類別。該函數只需要一個參數,即一個指向型態為WNDCLASS的結構指針。上面的程序使用的是windows.inc表頭文件中定義的WNDCLASSEX。
WNDCLASSEX STRUCT
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
在這里提示一下數據型態和匈牙利表示法:其中的lpfn前綴代表「指向函數的長指標」。(在Win32 API中,長指標和短指標(或者近程指標)沒有區別。這只是16位Windows的遺物。)cb前綴代表「字節數」而且通常作為一個常數來表示一個字節的大小。h前綴是一個句柄,而hbr前綴代表「一個畫刷的代號」。lpsz前綴代表「指向以0結尾字符串的指針」。
江湖上有一種傳說,說你讓不同的印度程序員編寫同樣的功能,他們的結果甚至連變量命名都相同。我想這個也許和他們都接受過統一的訓練有關。我比較推薦使用英文單詞作為變量名稱。五六年之后,我們翻閱自己的代碼看到:
Counter db ?
相比是一目了然的感覺,倘若命名為 jishuqi db ? 也許一時半會也猜不到這是什么東西。
我也不再著重說明指標的定義。一個程序寫作者的程序不應該因為使用以LP或NP為前綴的不同指針型態而被攪亂。
在WinMain中為WNDCLASS定義一個結構,通常像這樣:
wndclass :WNDCLASSEX
然后,你就可以初始化該結構的12個字段,并呼叫RegisterClass。
在WNDCLASS結構中最重要的兩個字段是第二個和最后一個,第二個字段(lpfnWndProc) 是依據這個類別來建立的所有窗口所使用的窗口消息處理程序的地址。在HELLOWIN.ASM中,這個是WndProc函數。最后一個字段是窗口類別的文字名稱。程序寫作者可以隨意定義其名稱。在只建立一個窗口的程序中,窗口類別名稱通常設定為程序名稱。
其它字段依照下面的方法描述了窗口類別的一些特征。讓我們依次看看WNDCLASS結構中的每個字段。
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
使用匯編語言的位「或」運算子結合了兩個「窗口類別樣式」標識符。在表頭文件Windows.inc中,已定義了一整組以CS為前綴的標識符:
CS_VREDRAW equ 1h
CS_HREDRAW equ 2h
CS_KEYCVTWINDOW equ 4h
CS_DBLCLKS equ 8h
CS_OWNDC equ 20h
CS_CLASSDC equ 40h
CS_PARENTDC equ 80h
CS_NOKEYCVT equ 100h
CS_NOCLOSE equ 200h
CS_SAVEBITS equ 800h
CS_BYTEALIGNCLIENT equ 1000h
CS_BYTEALIGNWINDOW equ 2000h
CS_PUBLICCLASS equ 4000h
CS_GLOBALCLASS equ CS_PUBLICCLASS
由于每個標識符都可以在一個復合值中設置一個位的值,所以按這種方式定義的標識符通常稱為「位旗標」。通常我們只使用少數的窗口類別樣式。HELLOWIN中用到的這兩個標識符表示,所有依據此類別建立的窗口,每當窗口的水平方向大小(CS_HREDRAW)或者垂直方向大小(CS_VREDRAW)改變之后,窗口要完全重畫。改變HELLOWIN的窗口大小,可以看到字符串仍然顯示在窗口的中央,這兩個標識符確保了這一點。不久我們就將看到窗口消息處理程序是如何得知這種窗口大小的變化的。
WNDCLASS結構的第三個字段由以下敘述進行初始化:
mov wndclass.lpfnWndProc,offset WndProc
這條敘述將這個窗口類別的窗口消息處理程序設定為WndProc,即HELLOWIN.ASM中的第二個函數。這個過程將處理依據這個窗口類別建立的所有窗口的全部消息。在匯編語言語言中,像這樣在結構中使用函數名時,真正提供的是指向函數的指針。
下面兩個字段用于在窗口類別結構和Windows內部保存的窗口結構中預留一些額外空間:
mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0
程序可以根據需要來使用預留的空間。HELLOWIN沒有使用它們,所以設定值為0。否則,和匈牙利表示法所指示的一樣,這個字段將被當成「預留的字節數」。
下一個字段就是程序的執行實體句柄(它也是WinMain的參數之一):
push hInst
pop wndclass.hInstance
敘述
invoke LoadIcon,NULL,IDI_APPLICATION
mov wndclass.hIcon,eax
為所有依據這個窗口類別建立的窗口設置一個圖標。圖標是一個小的位圖圖像,它對使用者代表程序,將出現在Windows工作列中和窗口的標題列的左端。在后面的章節中,您將學習如何為您的Windows程序自訂圖標。現在,為了方便起見,我們將使用預先定義的圖示。
要取得預先定義圖示的句柄,可以將第一個參數設定為NULL來呼叫LoadIcon。在加載程序寫作者自訂的圖標時(圖標應該存放在磁盤上的.EXE程序文件中),這個參數應該被設定為程序的執行實體句柄hInstance。第二個參數代表圖示。對于預先定義圖示,此參數是以IDI開始的標識符(「ID代表圖示」),標識符在windows.inc中定義。IDI_APPLICATION圖標是一個簡單的窗口小圖形。LoadIcon函數傳回該圖示的句柄。我們并不關心這個句柄的實際值,它只用于設置hIcon字段元的值。該字段在WNDCLASSEX結構中定義為HICON型態,此型態名的含義為「handle to an icon(圖示句柄)」。
敘述
invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax
與前一條敘述非常相似。LoadCursor函數加載一個預先定義的鼠標光標(命名為IDC_ARROW),并傳回該游標的句柄。該句柄被設定給WNDCLASS結構的hCursor字段。當鼠標光標在依據這個類別建立的窗口的顯示區域上出現時,它變成一個小箭頭。
下一個字段指定依據這個類別建立的窗口背景顏色。hbrBackground字段名稱中的hbr前綴代表「handle to a brush(畫刷句柄)」。畫刷是個繪圖詞匯,指用來填充一個區域的著色樣式。Windows有幾個標準畫刷,也稱為「備用(stock)」畫刷。這里所示的GetStockObject呼叫將傳回一個白色畫刷的句柄:
invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX
這意味著窗口顯示區域的背景完全為白色,這是一種極其普遍的做法。
下一個字段指定窗口類別菜單。HElLOWIN沒有應用程序菜單,所以該字段被設定為NULL:
mov wndclass.lpszMenuName,NULL
最后,必須給出一個類別名稱。對于小程序,類別名稱可以與程序名相同,即存放在szAppName變量中的「HelloWin」字符串。
mov wndclass.lpszClassName,offset szAppName
在初始化該結構的12個字段后,HELLOWIN呼叫RegisterClassEx來注冊這個窗口類別。該函數只有一個參數,即指向WNDCLASSEX結構的指針。實際上,RegisterClassA函數將獲得一個指向WNDCLASSA結構的指針,而RegisterClassW函數將獲得一個指向WNDCLASSW結構的指針。程序要使用哪個函數來注冊窗口類別,取決于發送給窗口的消息包含ASCII文字還是Unicode文字。
[原文程序中使用的是區分UNICODE和ANSI的API,現在我們使用的這個API同時支持2者,不過為了方便對照我仍然添加了這個判斷]
現在有一個問題:如果用定義的UNICODE標識符編譯了程序,程序將呼叫RegisterClassW。該程序可以在Microsoft Windows NT中執行良好。但如果此程序在Windows 98上執行,RegisterClassW函數并未真地被執行到。函數有一個進入點,但函數呼叫后只傳回0,表明錯誤。對于在Windows 98下執行的Unicode程序來說,這是一個通知使用者有問題并終止執行的好機會。這是本書中多數程序處理RegisterClass函數呼叫的方法:
invoke RegisterClassEx, ADDR wndclass
.if (EAX==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR
ret
.endif
由于MessageBoxW是可在Windows 98環境下執行的幾個Unicode函數之一,所以其執行正常。
當然,這段程序假定RegisterClassEx不會因為其它原因而呼叫失敗,諸如WNDCLASSEx結構中lpfnWndProc字段被設定成NULL之類的錯誤。GetLastError函數會幫助您確定在這樣的情況下產生錯誤的原因。GetLastError是Windows中常用的函數,它可以在函數呼叫失敗時獲得更多錯誤信息。不同函數的文件將指出您是否能夠用GetLastError來獲得這些信息。在Windows 98中呼叫RegisterClassW時,GetLastError將傳回120。在Windows.inc中您可以看到,值120與標識符ERROR_CALL_NOT_IMPLEMENTED相等。
很多Windows程序寫作者喜歡檢查所有可能發生錯誤的函數呼叫的傳回值。這么做確實有點道理,相信您也非常習慣在配置內存后檢查錯誤。而許多Windows函數需要配置內存。例如,RegisterClassEx需要配置內存,以保存窗口類別的信息。如此一來,您就應該要檢查這個函數的執行結果。另一方面說來,如果由于RegisterClass不能得到所需要的內存,它會聲明呼叫失敗,而Windows大概也快當掉了。
在本書的范例程序中,我做了最少的錯誤檢查。這不是因為我認為錯誤檢查不是一個好方法,而是因為這會讓我們在程序舉例中分心。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -