第七部分 附录


附录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 <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 /////////////////////////////////

CZVC编程网出品,一剑[QQ:28077188]整理编译,欢迎联系
MSN:loomman@hotmail.com
  更多精彩VC编程资源尽在CZVC编程网!