`
agn776rk
  • 浏览: 15058 次
最近访客 更多访客>>
社区版块
存档分类
最新评论

Programming Applications for Microsoft Windows - 第七部分 附录

阅读更多

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在所附光盘的根目录下。
  这个附录的其余部分将分别讨论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 /* _WIN32_WINNT >= 0x0500 */
  除非像我这样专门定义_ 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头文件
  /************************************************* *****************************
  Module:  CmnHdr.h
  Notices: Copyright (c) 2000 Jeffrey Richter
  Purpose: Common header file containing handy macros and definitions
  used throughout all the applications in the book.
  See Appendix A.
  ************************************************** ****************************/
  #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 /////////////// /* nonstandard extension 'single line comment' was used */ #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 ///////////////////////// /*  When the compiler sees a line like this:    #pragma chMSG(Fix this later) it outputs a line like this:   c:\CD\CmnHdr.h(82):Fix this later You can easily jump directly to this line and examine the surrounding code. */ #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) <= (Num)) && ((Num) <= (High))) //////////////////////////////// chDIMOF Macro //////////////////////////////// // This macro evaluates to the number of elements in an array.  #define chDIMOF(Array) (sizeof(Array) / sizeof(Array[0])) ///////////////////////////// chBEGINTHREADEX Macro /////////////////////////// // This macro function calls the C runtime's _beginthreadex function.  // The C runtime library doesn't want to have any reliance on 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 code    */  (Severity       ) |     \    /* MS(0) or Cust(1) */  (1         << 29) |     \    /* Reserved(0)      */  (0         << 28) |     \    /* Facility code    */  (Facility  << 16) |     \    /* Exception code   */  (Exception <<  0))) /////////////////////////// Quick MessageBox Macro //////////////////////////// inline void chMB(PCSTR s) {    char szTMP[128];    GetModuleFileNameA(NULL, szTMP, chDIMOF(szTMP));    MessageBoxA(GetActiveWindow(), s, szTMP, MB_OK); } //////////////////////////// Assert/Verify Macros ///////////////////////////// inline void chFAIL(PSTR szMsg) {    chMB(szMsg);    DebugBreak(); } // Put up an assertion failure message box. inline void chASSERTFAIL(LPCSTR file, int line, PCSTR expr) {    char sz[128];    wsprintfA(sz, "File %s, line %d : %s", file, line, expr);    chFAIL(sz); } // Put up a message box if an assertion fails in a debug build. #ifdef _DEBUG #define chASSERT(x) if (!(x)) chASSERTFAIL(__FILE__, __LINE__, #x) #else #define chASSERT(x) #endif // Assert in debug builds, but don't remove the code in retail builds. #ifdef _DEBUG #define chVERIFY(x) chASSERT(x) #else #define chVERIFY(x) (x) #endif /////////////////////////// chHANDLE_DLGMSG Macro ///////////////////////////// // The normal HANDLE_MSG macro in WindowsX.h does not work properly for dialog // boxes because DlgProc return a BOOL instead of an LRESULT (like // WndProcs). This chHANDLE_DLGMSG macro corrects the problem: #define chHANDLE_DLGMSG(hwnd, message, fn)                 \    case (message): return (SetDlgMsgResult(hwnd, uMsg,     \       HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) //////////////////////// Dialog Box Icon Setting Macro //////////////////////// // Sets the dialog box icons inline void chSETDLGICONS(HWND hwnd, int idi) {    SendMessage(hwnd, WM_SETICON, TRUE,  (LPARAM)        LoadIcon((HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE),           MAKEINTRESOURCE(idi)));    SendMessage(hwnd, WM_SETICON, FALSE, (LPARAM)        LoadIcon((HINSTANCE) GetWindowLongPtr(hwnd, GWLP_HINSTANCE),        MAKEINTRESOURCE(idi))); } /////////////////////////// OS Version Check Macros /////////////////////////// inline void chWindows9xNotAllowed() {    OSVERSIONINFO vi = { sizeof(vi) };    GetVersionEx(&vi);    if (vi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) {       chMB("This application requires features not present in Windows 9x.");       ExitProcess(0);    } } inline void chWindows2000Required() {    OSVERSIONINFO vi = { sizeof(vi) };    GetVersionEx(&vi);    if ((vi.dwPlatformId != VER_PLATFORM_WIN32_NT) && (vi.dwMajorVersion < 5)) {       chMB("This application requires features present in 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) <= 0) {          chMB("This application requires an OS that supports Unicode.");          ExitProcess(0);       }    } }; // "static" stops the linker from complaining that multiple instances of the // object exist when a single project contains multiple source files. static CUnicodeSupported g_UnicodeSupported; #endif /////////////////////////// Force 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,将会找到文件中包含下面代码行的部分:
  /* void Cls_OnCommand(HWND hwnd, int id, HWND hwndCtl,
  UINT codeNotify); */
  #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任务。建议读者了解并使用这些宏。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics