第七部分 附录
附录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建立都做上述两个改变。我可以在源代码中设定所有其他的编译程序和链接程序的设置,当你在你的项目中使用这里的任何源代码模块时,这些设置都将起作用。
所有的示例程序都要包含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
#if (_WIN32_WINNT >= 0x0500) WINBASEAPI BOOL WINAPI AssignProcessToJobObject( IN HANDLE hJob, IN HANDLE hProcess ); ... #endif /* _WIN32_WINNT >= 0x0500 */
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")
为了达到这个目的,需要使用一系列宏来修饰pragma message指令。可以这样使用c h M S G宏。
#pragma chMSG(Fix this later)
使用Microsoft Visual Developer Studio,在输出窗口上双击这一行,将会自动定位到相应文件的确切位置上。
C:\CD\CmnHdr.h(82):Fix this later
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 *);
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函数原型的方式实际上存在着两个问题。首先,用于这个函数的一些数据类型同用于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;
unsigned long __cdecl _beginthreadex( void *psa, unsigned long cbStack, unsigned (__stdcall *) (void *pvParam), void *pvParam, unsigned long fdwCreate, unsigned long *pdwThreadId);
HANDLE hThread = _beginthreadex(...);
HANDLE hThread = (HANDLE) _beginthreadex(...);
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)))
即使进程没有在一个调试程序下运行,有时候我也想在我的程序代码中强制一个断点。在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 <Windows.h> #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 /////////////////////////////////