Programming Applications for Microsoft Windows - 第七部分 附錄
2010年12月23日
csdn的闆塊太亂; 内部程式有錯誤。
第七部分 附錄
附錄A 建立環境
讀者要想建立本書中的示例程式,必須要對編譯程式和連結程式的開關選項進行設定。筆者試圖将這些設定方面的細節從示例程式中隔離出來,把所有這些設定放在一個頭檔案裡。這個頭檔案就是C m m H d r. h,它包含在所有示例程式的源代碼檔案中。
因為無法将所有的設定都放在這個頭檔案裡,我們對每個示例程式的項目設定做了一些改變。對每個項目,我們顯示Project Settings對話框,然後做下面所說的改變。
? 在G e n e r a l欄,設定Output Files目錄,這樣所有最終的. e x e和. d l l檔案都在一個目錄之下。
? 在C / C + +欄,選擇Code Generation 條目,并對Use Run-Time Library 字段選擇Multithreaded DLL。
這樣就可以了。我隻明确改變了兩個設定,而接受了其他所有的預設設定。注意要對每個項目的D e b u g建立和R e l e a s e建立都做上述兩個改變。我可以在源代碼中設定所有其他的編譯程式和連結程式的設定,當你在你的項目中使用這裡的任何源代碼子產品時,這些設定都将起作用。
A.1 CmmHdr.h頭檔案
所有的示例程式都要包含C m m H d r. h頭檔案,并且要在其他頭檔案之前包含。筆者編寫的C m m H d r. h列在清單A - 1裡。這個檔案給筆者帶來不少便利。這個檔案包含宏、連結程式指令、還有一些其他所有示例程式公用的内容。當我想做某些實驗時,我隻需修改并重建( r e b u i l d)所有的示例程式。C m m H d r. h在所附CD光牒的根目錄下。
這個附錄的其餘部分将分别讨論C m m H d r. h檔案的每一節,解釋每一節的基本原理,并描述在重建所有示例程式之前,如何及為什麼要對這個檔案進行修改。
A.1.1 Windows版本建立選項
因為有些示例程式調用了Microsoft Windows 2000中提供的新函數,本節定義_ W I N 3 2 _W I N N T符号如下:
#define _WIN32_WINNT 0x0500
這樣做是因為新的Windows 2000函數在Wi n d o w s頭檔案中被定義成下面這樣的原型:
#if (_WIN32_WINNT >= 0x0500)
WINBASEAPI
BOOL
WINAPI
AssignProcessToJobObject(
IN HANDLE hJob,
IN HANDLE hProcess
);
...
#endif
除非像我這樣專門定義_ W I N 3 2 _ W I N N T(在包含Wi n d o w s . h之前),否則這些新函數的原型就沒有被聲明,當試圖調用這些函數時,編譯程式将産生錯誤。微軟用_ W I N 3 2 _ W I N N T符号來保護這些函數,以使程式員開發的應用程式能夠運作在Windows 98及Windows NT的多個版本上。
A.1.2 Unicode建立選項
筆者編寫的所有這些示例程式既可按A N S I來編譯,也可按U n i c o d e來編譯。當針對x 8 6 C P U體系結構來編譯這些程式時, A N S I為預設選擇,這樣程式可以在Windows 98上執行。但對其他C P U體系結建構立程式就要用U n i c o d e,這樣程式可以占用較少的記憶體,并且執行得更快。
為了對x 8 6體系結建構立U n i c o d e版本,隻需将定義U N I C O D E的那一行代碼的注釋符去掉,并重建程式。通過在C m m H d r. h定義U N I C O D E宏,可以很容易地控制如何建立示例程式。關于U n i c o d e的詳細内容,可參見第2章。
A.1.3 視窗定義和第4級警告
筆者在開發軟體時,總是想保證代碼的編譯不受錯誤和警告的限制。我還喜歡在可能最高警告級上進行編譯,這樣編譯程式可以替我做大多數工作,甚至為我檢查很小的細節。對于Microsoft C/C++編譯程式,這将意味着我要使用第4級警告來建立示例程式。
遺憾的是,微軟的作業系統開發部在關于使用第4級警告做編譯方面,與我沒有共同的思想。其結果,當我使用第4級警告編譯示例程式時,Wi n d o w s頭檔案中的許多行引起編譯器産生警告。幸好,這些警告并不表示代碼中有問題。大多數情況是由于C語言中非傳統的用法所引起的,這些用法依賴編譯程式的擴充,幾乎所有與Wi n d o w s相容的編譯程式廠商都實作了這些擴充。
本節我確定警告級設定為3,而且C m m H d r. h包含标準的Wi n d o w s . h頭檔案。當包含了Wi n d o w s . h時,在我編譯其餘代碼時就設定第4級警告。在第4級警告上,編譯程式對那些我不認為有問題的内容發出"警告",這樣我通過使用#pragma warning指令顯式地告訴編譯程式忽略某些良性的警告錯。
A.1.4 Pragma消息幫助宏
在我編寫代碼時,我喜歡讓代碼的某些部分能夠立即運作起來,然後再完善它。為了提醒自己要特别注意某些代碼,我習慣于加入下面這樣一行代碼:
#pragma message("Fix this later")
當編譯程式對這一行進行編譯時,它會輸出一個字元串提醒我還需要再做一些工作。但這條消息不怎麼有用。我決定尋找一種辦法,讓編譯程式輸出源代碼檔案的名字,以及p r a g m a出現的行号。這樣,我不光知道要做一些工作,而且能夠立刻确定在什麼地方做。
為了達到這個目的,需要使用一系列宏來修飾pragma message指令。可以這樣使用c h M S G宏。
#pragma chMSG(Fix this later)
當編譯程式編譯上面這一行代碼時,會産生這樣一行内容:
使用Microsoft Visual Developer Studio,在輸出視窗上輕按兩下這一行,将會自動定位到相應檔案的确切位置上。
C:\CD\CmnHdr.h(82):Fix this later
還有一個友善之處, c h M S G宏不要求對文本串使用引号。
A.1.5 chINRANGE和chDIMOF宏
我時常在編寫程式時使用這兩個友善有用的宏。第一個宏c h I N R A N G E,用來檢視一個數值是否在另外兩個數值之間。第二個宏c h D I M O F,隻是傳回一個數組中元素的數目。這個宏是用s i z e o f操作符先計算整個數組的位元組數,然後再用這個數除以數組中一個資料項所占的位元組數,進而得出結果。
A.1.6 chBEGINTHREADEX宏
本書中的所有多線程示例程式都使用了微軟的C/C + +運作時函數庫中的_ b e g i n t h r e a d e x函數,而不是作業系統的C r e a t e T h r e a d函數。我使用這個函數是因為_ b e g i n t h r e a d e x函數為新線程做好了準備,使新線程能夠使用C / C + +運作時函數庫中的函數,而且還因為它保證線上程傳回時清除每個線程的C / C + +運作時庫資訊(見第6章有關細節)。但遺憾的是_ b e g i n t h r e a d e x函數的原型是這樣的。
tunately, the _beginthreadex function is proto
unsigned long __cdecl _beginthreadex(
void *,
unsigned,
unsigned (__stdcall *)(void *),
void *,
unsigned,
unsigned *);
盡管_ b e g i n t h r e a d e x函數用的參數值同C r e a t e T h r e a d函數用的參數值是一樣的,但二者的參數的資料類型都不相比對。C r e a t e T h r e a d函數的原型是這樣的:
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(PVOID pvParam);
HANDLE CreateThread(
PSECURITY_ATTRIBUTES psa,
DWORD cbStack,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadId); 微軟在建立_ b e g i n t h r e a d e x函數的原型時沒有使用Wi n d o w s資料類型。這是因為微軟的C / C + +運作時庫開發組不想對作業系統開發組有任何依賴。這使得_ b e g i n t h r e a d e x函數的使用更加困難。
微軟定義_ b e g i n t h r e a d e x函數原型的方式實際上存在着兩個問題。首先,用于這個函數的一些資料類型同用于C r e a t e T h r e a d函數的原始類型不相比對。例如Wi n d o w s資料類型D W O R D的定義是這樣的:
typedef unsigned long DWORD;
這個資料類型用于C r e a t e T h r e a d函數的c b S t a c k參數以及f d w C r e a t e參數。問題是函數_ b e g i n t h r e a d e x将這兩個參數的原型定義為u n s i g n e d,實際意思是unsigned int。編譯程式将unsigned int看成是與unsigned long不同的東西,并且産生一個警告。_ b e g i n t h r e a d e x函數不屬于标準的C / C + +運作時函數庫,隻是作為調用C r e a t e T h r e a d函數的替代手段而存在,是以微軟應該按下面的形式來定義_ b e g i n t h r e a d e x的原型,這樣就不會産生警告了:
unsigned long __cdecl _beginthreadex(
void *psa,
unsigned long cbStack,
unsigned (__stdcall *) (void *pvParam),
void *pvParam,
unsigned long fdwCreate,
unsigned long *pdwThreadId);
第二個問題是第一個問題的一個小變種。_ b e g i n t h r e a d e x函數傳回一個unsigned long型的值,代表建立立線程的句柄。程式中通常用H A N D L E型資料變量來儲存這個傳回值:
HANDLE hThread = _beginthreadex(...);
上面這行代碼又使編譯程式産生另一個警告錯。為了避免編譯程式警告,必須改寫這一行代碼,引入一個轉換(c a s t):
HANDLE hThread = (HANDLE) _beginthreadex(...);
這又是一個不友善之處。為了友善起見,我在C m n H d r. h中定義了一個c h B E G I N T H RE A D E X宏,替我執行所有這些轉換:
typedef unsigned (__stdcall *PTHREAD_START) (void *);
#define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \
pvParam, fdwCreate, pdwThreadId) \
((HANDLE)_beginthreadex( \
(void *) (psa), \
(unsigned) (cbStack), \
(PTHREAD_START) (pfnStartAddr), \
(void *) (pvParam), \
(unsigned) (fdwCreate), \
(unsigned?) (pdwThreadId)))
A.1.7 對x86平台的調試斷點改進
即使程序沒有在一個調試程式下運作,有時候我也想在我的程式代碼中強制一個斷點。在Wi n d o w s中要做這件事,可以讓線程調用D e b u g B r e a k函數。這個函數在k e r n e l 3 2 . d l l中,可以使一個調試程式同程序挂接。當調試程式被挂接上時,指令指針就定位在引起斷點的C P U指令上。這個指令包含在k e r n e l 3 2 . d l l中的D e b u g B r e a k函數裡,是以為了看到我的源代碼,我必須在D e b u g B r e a k函數之外單步執行。
在x 8 6體系結構上,通過執行"i n t 3"C P U指令來做一個斷點。是以,在x 8 6平台之上,我定義D e b u g B r e a k作為這個内聯的彙編語言指令。當我的D e b u g B r e a k執行時,我不是在k e r n e l 3 2 . d l l中調用。斷點發生在我的代碼中,指令指針定位在下一個C/C++語句中。這樣就友善多了。
A.1.8 建立軟體異常代碼
當處理軟體異常時,必須建立你自己的3 2位異常代碼。這些代碼遵循特定的格式(見第2 4章的讨論)。為了更容易地建立這些代碼,我使用M A K E S O F T WA R E E X C E P T I O N宏。
A.1.9 chMB宏
c h M B宏隻是顯示一個消息框。消息框的标題是調用程序可執行代碼的全路徑名。
A.1.10 chASSERT和chVERIFY宏
在我開發這些示例程式時,為了查找潛在的問題,我在整個代碼中多處使用c h A S S E RT宏。這個宏測試由x所辨別的表達式是否為T R U E,如果不是,則顯示一個消息框指出失敗的檔案、行和表達式。在程式的發行建立中,這個宏什麼也不做。c h V E R I F Y宏與c h A S S E RT宏差不多,差別在于不論是調試建立(debug build)還是發行建立(release build),c h V E R I F Y都要對表達式進行測試。
A . 1 . 11 chHANDLE_DLGMSG宏
當你通過對話框使用消息分流器時,不應該使用微軟的Wi n d o w s X . h 頭檔案中的H A N D L E _ M S G宏,因為這個宏并不能傳回T R U E或FA L S E來指出消息是否由對話框的過程來處理。我定義的c h H A N D L E _ D L G M S G宏會通知視窗消息的傳回值,适當地處理傳回值,以便在一個對話框過程中使用。
A.1.12 chSETDLGICONS宏
由于多數示例程式使用一個對話框作為主視窗,你必須手工改變對話框圖示,以便讓它正确地顯示在Ta s k b a r(任務條)、任務切換視窗和程式本身的标題上。當對話框接收到一個W M _ I N I T D I A L O G消息時,總要調用c h S E T D L G I C O N S宏,以正确設定圖示。
A.1.13 OS版本檢查内聯函數
本書的大多數示例程式可運作在所有平台上,但也有一些程式要求一些Windows 95和Windows 98所不支援的特性,有些程式要求一些隻在Windows 2000中提供的特性。每個程式在初始化時要檢查宿主系統的版本,如果要求更适用的作業系統時,就顯示一個通知。
對那些不能在Windows 95和Windows 98上運作的程式,你會看到,在程式的_ t Wi n M a i n函數中有一個對Wi n d o w s 9 x N o t A l l o w e d函數的調用。對于要求Windows 2000的示例程式,你會看到在程式的_ t Wi n M a i n函中有一個對c h Wi n d o w s 2 0 0 0 R e q u i r e d函數的調用。
A.1.14 确認宿主系統是否支援Unicode
Windows 98不能像Windows 2000那樣完全支援U n i c o d e。實際上,調用U n i c o d e函數的程式不能在Windows 98上運作。但遺憾的是,如果調用一個為U n i c o d e編譯的程式,Wi n d o w s 9 8不會給出任何通知資訊。對本書中的程式,這意味着這些程式從開始到結束,都不會有它們想執行的提示資訊。
這确實是一個難題。我需要有一種辦法能夠知道我的程式是對U n i c o d e建立的,但可能在Windows 98系統上運作。是以我建立了一個CUnicodeSupported C++類。這個類的構造函數隻是檢查宿主系統是不是對U n i c o d e有良好的支援,如果不是,就顯示一個消息框,并且程序結束。
讀者會看到在C m n H d r. h中,我建立了這個類的一個全局的靜态執行個體。當我的程式啟動時,C / C + +運作時庫啟動代碼調用這個對象的構造函數。如果這個構造函數檢測到作業系統完全支援U n i c o d e,構造函數傳回而程式繼續執行。通過建立這個類的全局執行個體,我不需要在每個示例程式的源代碼子產品中再增加特殊的代碼。對于非U n i c o d e的程式建立,不需要聲明或執行個體化上述的C + +類。讓程式隻管運作就是。
A.1.15 強制連結程式尋找(w)WinMain進入點函數
本書以前版本的一些讀者,将書中我的源代碼子產品添加到他們自己的Vi s u a l C + +項目中,但在建立項目時出現連結錯誤。問題的原因是他們建立了Win32 Console Application項目,導緻連結程式去尋找( w ) m a i n進入點函數。因為本書中所有示例程式都是G U I程式,是以我的代碼都有一個_ t Wi n M a i n進入點函數。這就是連結程式為什麼要報錯。
我的回答是,他們應該删除原來的項目,用Visual C++建立新的Win32 Application項目(注意在項目類型中不能出現" C o n s o l e"一詞),再将我的源代碼加進去。連結程式尋找一個( w ) Wi n M a i n進入點函數,而這在我的代碼中已提供,項目應該能夠建立。
為了減少我收到的有關這個問題的電子郵件的數量,我在C m n H d r. h中加入了一個p r a g m a,強制連結程式去尋找( w ) Wi n M a i n進入點函數,即使是用Visual C++建立了一個Win32 ConsoleA p p l i c a t i o n項目。
在第4章,我詳細說明了Visual C++項目類型的有關内容,連結程式如何選擇進入點函數,及如何重載連結程式的預設動作等。下面的清單A - 1是Cmn Hdr. h 頭檔案。
清單A-1 CmnHdr. h頭檔案
#pragma once // Include this header file once per compilation unit
Windows Version Build Option / #define _WIN32_WINNT 0x0500 //#define WINVER 0x0500 Unicode Build Option / // If we are not compiling for an x86 CPU, we always compile using Unicode. #ifndef _M_IX86 #define UNICODE #endif // To compile using Unicode on the x86 CPU, uncomment the line below. //#define UNICODE // When using Unicode Windows functions, use Unicode C-Runtime functions too. #ifdef UNICODE #define _UNICODE #endif / Include Windows Definitions / #pragma warning(push, 3) #include #pragma warning(pop) #pragma warning(push, 4) / Verify that the proper header files are being used // #ifndef WT_EXECUTEINPERSISTENTIOTHREAD #pragma message("You are not using the latest Platform SDK header/library ") #pragma message("files. This may prevent the project from building correctly.") #endif // Allow code to compile cleanly at warning level 4 /// #pragma warning(disable:4001) // unreferenced formal parameter #pragma warning(disable:4100) // Note: Creating precompiled header #pragma warning(disable:4699) // function not inlined #pragma warning(disable:4710) // unreferenced inline function has been removed #pragma warning(disable:4514) // assignment operator could not be generated #pragma warning(disable:4512) / Pragma message helper macro / #define chSTR2(x) #x #define chSTR(x)chSTR2(x) #define chMSG(desc) message(__FILE__ "(" chSTR(__LINE__) "):" #desc) // chINRANGE Macro // This macro returns TRUE if a number is between two others #define chINRANGE(low, Num, High) (((low) Windows' data // types such as HANDLE. This means that a Windows programmer needs to cast // values when using _beginthreadex. Since this is terribly inconvenient, // I created this macro to perform the casting. typedef unsigned (__stdcall *PTHREAD_START) (void *); #define chBEGINTHREADEX(psa, cbStack, pfnStartAddr, \ pvParam, fdwCreate, pdwThreadId) \ ((HANDLE)_beginthreadex( \ (void *) (psa), \ (unsigned) (cbStack), \ (PTHREAD_START) (pfnStartAddr), \ (void *) (pvParam), \ (unsigned) (fdwCreate), \ (unsigned *) (pdwThreadId))) // DebugBreak Improvement for x86 platforms /// #ifdef _X86_ #define DebugBreak() _asm { int 3 } #endif /// Software Exception Macro // // Useful macro for creating your own software exception codes #define MAKESOFTWAREEXCEPTION(Severity, Facility, Exception) \ ((DWORD) ( \ (Severity ) | \ (1 Windows 9x."); ExitProcess(0); } } inline void chWindows2000Required() { OSVERSIONINFO vi = { sizeof(vi) }; GetVersionEx(&vi); if ((vi.dwPlatformId != VER_PLATFORM_WIN32_NT) && (vi.dwMajorVersion Windows 2000."); ExitProcess(0); } } / UNICODE Check Macro / // Since Windows 98 does not support Unicode, issue an error and terminate // the process if this is a native Unicode build running on Windows 98 // This is accomplished by creating a global C++ object. Its constructor is // executed before WinMain. #ifdef UNICODE class CUnicodeSupported { public: CUnicodeSupported() { if (GetWindowsDirectoryW(NULL, 0) Windows subsystem /// #pragma comment(linker, "/subsystem:Windows") / End of File / 附錄B 消息分流器、子控件宏和API宏
每當我參加一些會議時,常問一些人是不是使用消息分流器,而回答通常是" N o"。我再進一步深究這件事,發現很多人不知道消息分流器是幹什麼用的,甚至沒有聽說過它。在本書中,通過使用帶有消息分流器的C / C + +編寫示例代碼,我想向大家介紹這種不大為人所知但很有用的宏。
消息分流器定義在Microsoft Visual C++中提供的Wi n d o w s X . h檔案裡。通常在Wi n d o w s . h檔案之後緊接着包含這個檔案。Wi n d o w s X . . h檔案就是一組# d e f i n e指令,建立了一組供我們使用的宏。Wi n d o w s X . h的宏實際上分為三組:消息分流器、子控件宏和A P I宏。這些宏以下述的方式為我們提供幫助:
? 利用這些宏可以減少程式中要做的轉換( c a s t i n g)的數量,并可使所要求的轉換是無錯誤的。使用C / C + +的Wi n d o w s程式設計中一個大的問題是所要求的轉換數量。你很難看到一個不要求某種轉換的Wi n d o w s函數調用。但應該盡量避免使用轉換,因為轉換阻礙編譯器發現代碼中的潛在錯誤。一個轉換是在告訴編譯程式:"我知道我在這裡傳遞了錯誤的轉換,但就要這樣做。我知道我在幹什麼。"當你做了許多轉換時,就很容易出錯。編譯程式應該盡可能對此提供幫助。
? 使代碼的可讀性更好。
? 可簡化1 6位Wi n d o w s、3 2位Wi n d o w s和6 4位Wi n d o w s之間的代碼移植工作。
? 易于了解(隻是一些宏)
? 這些宏容易結合到已有的代碼中。可以不管老的代碼而立即在新的代碼中使用這些宏。不必修改整個程式。
? 在C和C + +代碼中都可以使用這些宏,盡管當使用C + +類時它們不是必需的。
? 如果需要某一個特性,而這些宏不直接支援這個特性,可以根據這個頭檔案中的宏,很容易地編寫自己的宏。
? 不需要參照或記住費解的Wi n d o w s構造。例如,許多Wi n d o w s中的函數,要求一個l o n g型參數,其中這個長參數的高字( h i g h - w o r d)的值代表一個東西,而其低字( l o w - w o r d)又代表另一個東西。在調用這個函數之前,你必須用兩個單獨的值構造一個l o n g型值。通常利用Wi n D e f . h中的M A K E L O N G宏來做這種事。我簡直記不清有多少次把兩個值的次序給弄反了,造成對函數傳遞了一個錯誤的值。而Wi n d o w s X . h中的宏可以幫我們的忙。
B.1 消息分流器
消息分流器(message cracker)使視窗過程的編寫更加容易。通常,視窗過程是用一個大的s w i t c h語句實作的。在我的經驗中,我見過有的視窗過程的s w i t c h語句包含5百多行代碼。我們都知道按這種方式實作視窗過程是一種壞的習慣,但我們都這麼做過。而利用消息分流器可将s w i t c h語句分成小的函數,每個視窗消息對應一個函數。這樣使代碼更容易管理。
有關視窗過程的另一個問題是每個消息都有w P a r a m和l P a r a m參數,并且根據消息的不同,這些參數的意思也不同。在某些情況下,如對W M _ C O M M A N D消息,w P a r a m包含兩個不同的值。w P a r a m參數的高字是通知碼,而低字是控件的I D。或者是反過來?我總是忘了次序。如果使用消息分流器,就不用記住或查閱這些内容。消息分流器之是以這樣命名,是因為它們對任何給定的消息進行分流。為了處理W M _ C O M M A N D消息,你隻需編寫這樣一個函數:
void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl,
UINT codeNotify)
{
switch(id)
{
case ID_SOMELISTBOX:
if(codeNotify != LBN_SELCHANGE)
break;
// Do LBN_SELCHANGE processing.
break;
case ID_SOMEBUTTON:
break;
}
}
這是多麼容易!分流器檢視消息的w P a r a m和l P a r a m參數,将參數分開,并調用你的函數。
為了使用消息分流器,必須對你的視窗過程的s w i t c h語句做一些修改。看一看下面的視窗過程:
LRESULT WndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand);
HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);
HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);
default:
return(DefWindowProc(hwnd, uMsg, wParam, lParam));
}
}
H A N D L E _ M S G宏在Wi n d o w s X . h中是這樣定義的:
#define HANDLE_MSG(hwnd, message, fn) \
case(message): \
return HANDLE_##message((hwnd), (wParam), (lParam), (fn));
對于W M _ C O M M A N D消息,預處理程式把這一行代碼擴充成下面的代碼:
case(WM_COMMAND):
return HANDLE_WM_COMMAND((hwnd),(wParam), (lParam),
(Cls_OnCommand));
定義在WindowsX.h 中的各H A N D L E _ W M _ *宏是實際的消息分流器。它們分流w P a r a m參數和l P a r a m參數,執行所有必要的轉換,并調用适當的消息函數,如前面例舉過的C l s _O n C o m m a n d函數。H A N D L E _ W M _ C O M M A N D宏的定義如下:
#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \
( (fn) ((hwnd), (int) (LOWORD(wParam)), (HWND)(lParam),
(UINT) HIWORD(wParam)), 0L)
當預處理程式擴充這個宏時,其結果是用w P a r a m和l P a r a m參數的内容分流成各自的部分并經适當轉換,來調用C l s _ O n C o m m a n d函數。
在使用消息分流器來處理一個消息之前,應該打開Wi n d o w s X . h檔案并搜尋要處理的消息。例如,如果搜尋W M _ C O M M A N D,将會找到檔案中包含下面代碼行的部分:
#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) \
((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam), \
(UINT)HIWORD(wParam)), 0L) #define FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify, fn) \ (void)(fn)((hwnd), WM_COMMAND, \ MAKEWPARAM((UINT)(id),(UINT)(codeNotify)), \ (LPARAM)(HWND)(hwndCtl)) 第一行是注釋行,展示要編寫的函數原型。下一行是H A N D L E _ W M _ *宏,我們已經讨論過。最後一行是消息轉發器( f o r w a r d e r)。假定在你處理W M _ C O M M A N D消息時,你想調用預設的視窗過程,并讓它為你做事。這個函數應該是這個樣子:
void Cls_OnCommand (HWND hwnd, int id, HWND hwndCtl,
UINT codeNotify)
{
// Do some normal processing.
// Do default processing.
FORWARD_WM_COMMAND(hwnd, id, hwndCtl, codeNotify,
DefWindowProc);
}
F O RWA R D _ W M _ *宏将分流開的消息參數重新構造成等價的w P a r a m和l P a r a m。然後這個宏再調用你提供的函數。在上面的例子中,宏調用D e f Wi n d o w P r o c函數,但你可以簡單地使用S e n d M e s s a g e或P o s t M e s s a g e。實際上,如果你想發送(或登記)一個消息到系統中的任何視窗,可以使用一個F O RWA R D _ W M _ *宏來幫助合并各個參數。
B.2 子控件宏
子控件宏(Child Control Macro)使發送消息到子控件變得更加容易。這些宏同F O RWA R O _W M _ *宏很相似。每個宏的定義以一個控件類型開始(這個控件是要對它發送消息的控件),後面跟一個下橫線和消息名。例如,向一個清單框發送一個L B _ G E T C O U N T消息,就使用Wi n d o w s X . h中的這個宏:
#define ListBox_GetCount(hwndCtl) \
((int)(DWORD)SendMessage((hwndCtl), LB_GETCOUNT, 0, 0L))
關于這個宏我想說兩件事。第一,它隻用一個參數h w n d C t l,這是清單框的視窗句柄。因為L B _ G E T C O U N T消息忽略了w P a r a m和l P a r a m參數,你不必再管這些參數。你可以看到,宏隻傳遞了零。第二,當S e n d M e s s a g e傳回時,結果被轉換成i n t,是以你不必提供你自己的轉換。
關于子控件宏,有一件事我不喜歡,就是這些宏要用控件視窗的句柄。許多時候,你要發送消息到一個控件,而這個控件是一個對話框的子控件。是以最終你總要調用G e t D l g I t e m,産生這樣的代碼:
int n = ListBox_GetCount(GetDlgItem(hDlg, ID_LISTBOX));
比起使用S e n d D l g I t e m M e s s a g e,這個代碼的運作雖然不慢,但你的程式會包含一些額外的代碼。這是由于對G e t D l g I t e m的額外調用。如果需要對同一控件發送幾個消息,你可能想調用一次G e t D l g I t e m,儲存子視窗的句柄,然後調用你需要的所有的宏,見下面的代碼:
HWND hwndCtl = GetDlgItem(hDlg, ID_LISTBOX);
int n = ListBox_GetCount(hwndCtl);
ListBox_AddString(hwndCtl, "Another string");
如果按這種方式設計你的代碼,你的程式會運作得更快,因為這樣就不會反複地調用G e t D l g I t e m。如果你的對話框有許多控件并且你要尋找的控件在Z序的結尾,則G e t D l g I t e m可能是個很慢的函數。
B.3 API宏
A P I宏可以簡化某些常用的操作,如建立一種新字型,選擇字型到裝置環境,儲存原來字型的句柄。代碼的形式如下:
HFONT hfontOrig = (HFONT) SelectObject(hdc, (HGDIOBJ) hfontNew);
這個語句要求兩個轉換以得到沒有編譯警告錯誤的編譯。在Wi n d o w s X . h中有一個宏,正是為了這個用途而設計:
#define SelectFont(hdc, hfont) \
((HFONT) SelectObject( (hdc), (HGDIOBJ) (HFONT) (hfont)))
如果你使用這個宏,你的程式中的代碼行就變成:
HFONT hfontOrig = SelectFont(hdc, hfontNew);
這行代碼更容易讀,也不容易出錯。
在Wi n d o w s X . h中還有其他一些A P I宏,有助于常用的Wi n d o w s任務。建議讀者了解并使用這些宏。