自Windows NT起,Windows的所有版本都完全用Unicode来构建。也就是说,所有核心函数(创建窗口、显示文本、进行字符串处理等等)都需要Unicode字符串。调用一个Windows函数时,如果向它传入一个ANSI字符串(由单字节字符组成的一个字符串),函数首先会把字符串转换为Unicode,再把结果传给操作系统。如果希望函数返回ANSI字符串,那么操作系统会先把Unicode字符串转换为ANSI字符串,再把结果返回给你的应用程序。所有这些转换都是悄悄地进行的。当然,为了执行这些字符串转换,系统会产生时间和内存上的开销。
如果一个Windows函数需要获取一个字符串作为参数,则该函数通常有两个版本。例如,一个CreateWindowEx接受Unicode字符串,另一个CreateWindowEx则接受ANSI字符串。这没错,但两个函数的原型实际是这样的:
HWND WINAPI CreateWindowExW(
DWORD dwExStyle,
PCWSTR pClassName, // A Unicode string
PCWSTR pWindowName, // A Unicode string
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
PVOID pParam);
HWND WINAPI CreateWindowExA(
DWORD dwExStyle,
PCSTR pClassName, // An ANSI string
PCSTR pWindowName, // An ANSI string
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
PVOID pParam);
CreateWindowExW这个版本接受Unicode字符串。函数名末尾的大写字母W代表wide。Unicode字符都是16位宽,所以它们常常被称作宽(wide)字符。CreateWindowExA 末尾的大写字母A表明该函数接受ANSI字符串。
但在平时,我们只是在自己的代码中调用CreateWiCreateWindowExW或CreateWindowExA。在WinU宏,它的定义如下:
#ifdef UNICODE
#define CreateWindowEx CreateWindowExW
#else
#define CreateWindowEx CreateWindowExA
#endif
编译源代码模块时,是否定义UNICODE决定了要调用哪一个版本的CreateWindowEx。用Visual Studio创建一个新项目的时候,它默认会定义UNICODE。所以,在默认情况下,对CreateWindowEx的任何调用都会扩展宏来调用CreateWindowExW——即Unicode版本的CreateWindowEx。
C运行库中的Unicode函数和ANSI函数
和Windows函数一样,C运行库提供了一系列函数来处理ANSI字符和字符串,并提供了另一系列函数来处理Unicode字符与字符串。然而,与Windows不同的是, ANSI版本的函数是“自力更生”的:它们不会把字符串转换为Unicode形式,再从内部调用函数的Unicode版本。当 然,Unicode版本的函数也是“自力更生”的,它们不会在内部调用ANSI版本。
在C运行库中,能返回ANSI字符串长度的一个函数的例子是strlen。与之对应的是wcslen,这个C运行库函数能返回Unicode字符串的长度。
这两个函数的原型都在String.h中。为了使你的源代码针对ANSI或Unicode都能编译,那么还必须包含TChar.h,该文件定义了以下宏:
#ifdef _UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
现在,在你的代码中应该调用_tcslen。如果已经定义了_UNICODE,它会扩展为wcslen;否则,它会扩展为strlen。默认情况下,在Visual Studio中新建一个C++项目时,已经定义了_UNICODE(就像已经定义了UNICODE一样)。针对不属于C++标准一部分的标识符,C运行库始终为其附加下划线前缀。但是,Windows团队没有这样做。所以,在你的应用程序中,应确保要么同时定义了UNICODE和_UNICODE,要么一个都不要定义。附录A将详细描述CmnHdr.h;本书所有示例代码都将用这个头文件来避免这种问题。