2.3 宽字符和WINDOWS
Windows NT从底层支援Unicode。即Windows NT内部使用由16位字符组成的字串。Windows NT可执行为ASCII、Unicode或者ASCII和Unicode混合编写的程序。
只有很少的Windows 98函数调用支援宽字串(这些函数列在《Microsoft Knowledge Base article Q125671》中;它们包括MessageBox)。
2.3.1 Windows头文件类型
一个Windows程序包括头文件WINDOWS.H。该档案包括WINDEF.H。
在WINDEF.H中有许多在Windows中使用的基本型态定义,并包括WINNT.H。
WINNT.H处理基本的Unicode支援。WINNT.H的前面包含C的头文件CTYPE.H,这是C的众多头文件之一,包括wchar_t的定义。WINNT.H定义了新的数据类型,称作CHAR和WCHAR:
typedef char CHAR ;
typedef wchar_t WCHAR ; // wc
当您需要定义8位字符或者16位字符时,推荐您在Windows程序中使用的数据类型是CHAR和WCHAR。WCHAR定义後面的注释是匈牙利标记法的建议:一个基於WCHAR数据类型的变量可在前面附加上字母wc以说明一个宽字符。
WINNT.H头文件还定义了可用做8位字串指针的6种数据类型和4个可用做const 8位字串指针的数据类型。这里是头文件中一些实用的数据类型语句:
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR ;
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR ;
字首N和L表示「near」和「long」,指的是16位Windows中两种大小不同的指针。在Win32中near和long指针没有区别。
同样,WINNT.H定义了6种可作为16位字串指针的数据类型和4种可作为const 16位字串指针的数据类型:
typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR ;
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ;
至此,我们有了数据类型CHAR(一个8位的char)和WCHAR(一个16位的wchar_t),以及指向CHAR和WCHAR的指针。
与TCHAR.H一样,WINNT.H将TCHAR定义为一般的字符类型。如果定义了识别字UNICODE(没有下横线),则TCHAR和指向TCHAR的指针就分别定义为WCHAR和指向WCHAR的指针;如果没有定义识别字UNICODE,则TCHAR和指向TCHAR的指针就分别定义为char和指向char的指针:
#ifdef UNICODE
typedef WCHAR TCHAR, * PTCHAR ;
typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ;
typedef LPCWSTR LPCTSTR ;
#else
typedef char TCHAR, * PTCHAR ;
typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ;
typedef LPCSTR LPCTSTR ;
#endif
如果已经在某个头文件或者其他头文件中定义了TCHAR数据类型,那么WINNT.H和WCHAR.H头文件都能防止其重复定义。不过,无论何时在程序中使用其他头文件时,都应在所有其他头文件之前包含WINDOWS.H。
WINNT.H头文件还定义了一个宏定义,该宏定义将L添加到字串的第一个引号前。如果定义了UNICODE识别字,则一个称作 __TEXT的宏定义定义如下:
#define __TEXT(quote) L##quote
如果没有定义识别字UNICODE,则像这样定义__TEXT宏定义:
#define __TEXT(quote) quote
此外, TEXT宏定义可这样定义:
#define TEXT(quote) __TEXT(quote)
这与TCHAR.H中定义_TEXT宏定义的方法一样,只是不必担心下横线。我将在本书中使用这个宏定义的TEXT版本。
这些定义可使您在同一程序中混合使用ASCII和Unicode字串,或者编写一个可被ASCII或Unicode编译的程序。如果您希望明确定义8位字符变量和字串,请使用CHAR、PCHAR(或者其他),以及带引号的字串。为明确地使用16位字符变量和字串,请使用WCHAR、PWCHAR,并将L添加到引号前面。对於是8位还是16位取决於UNICODE识别字的定义的变量或字串,要使用TCHAR、PTCHAR和TEXT宏定义。
2.3.2 Windows函数调用
从Windows 1.0到Windows 3.1的16位Windows中,MessageBox函数位於动态连结程序库USER.EXE。在Windows 3.1软件开发套件的WINDOWS.H中,MessageBox函数定义如下:
int WINAPI MessageBox (HWND, LPCSTR, LPCSTR, UINT) ;
注意,函数的第二个、第三个参数是指向常数字串的指针。
32位的Windows(即所有版本的Windows NT,以及Windows 95和Windows 98)除了含有与16位相容的USER.EXE以外,还含有一个称为USER32.DLL的动态连结程序库,该动态连结程序库含有32位使用者介面函数的进入点,包括32位的MessageBox。
这就是Windows支援Unicode的关键:在USER32.DLL中,没有32位MessageBox函数的进入点。实际上,有两个进入点,一个名为MessageBoxA(ASCII版),另一个名为MessageBoxW(宽字符版)。用字串作参数的每个Win32函数都在作业系统中有两个进入点!
下面是MessageBoxA在WINUSER.H中定义的方法。这与MessageBox早期的定义很相似:
WINUSERAPI int WINAPI MessageBoxA ( HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) ;
下面是MessageBoxW:
WINUSERAPI int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText,
LPCWSTR lpCaption, UINT uType) ;
注意,MessageBoxW函数的第二个和第三个参数是指向宽字符的指针。
根据是否定义了UNICODE,MessageBox将与MessageBoxA或MessageBoxW一样。
#ifdef UNICODE
#define MessageBox MessageBoxW
#else
#define MessageBox MessageBoxA
#endif
这样,如果定义了UNICODE识别字,那么程序中所有的MessageBox函数调用实际上就是MessageBoxW函数;否则,就是MessageBoxA函数。
2.3.3 Windows的字串函数
下面是Windows定义的一组字串函数,这些函数用来计算字串长度、复制字串、连接字串和比较字串:
ILength = lstrlen (pString) ;
pString = lstrcpy (pString1, pString2) ;
pString = lstrcpyn (pString1, pString2, iCount) ;
pString = lstrcat (pString1, pString2) ;
iComp = lstrcmp (pString1, pString2) ;
iComp = lstrcmpi (pString1, pString2) ;
这些函数与C程序库中对应的函数功能相同。如果定义了UNICODE识别字,那么这些函数将接受宽字串,否则只接受常规字串。宽字串版的lstrlenW函数可在Windows 98中执行。
2.3.4 在Windows中使用printf
如果您从未使用过sprintf (我第一次开始写Windows程序时也没用过此函数),这里有一个简短的执行实例,printf函数说明如下:
int printf (const char * szFormat, ...) ;
第一个参数是一个格式字串,後面是与格式字串中的代码相对应的不同类型多个参数。
sprintf函数定义如下:
int sprintf (char * szBuffer, const char * szFormat, ...) ;
第一个参数是字符缓冲区;後面是一个格式字串。Sprintf不是将格式化结果标准输出,而是将其存入szBuffer。该函数返回该字串的长度。
在文字模式程序设计中,
printf ("The sum of %i and %i is %i", 5, 3, 5+3) ;
的功能相同於
char szBuffer [100] ;
sprintf (szBuffer, "The sum of %i and %i is %i", 5, 3, 5+3) ;
puts (szBuffer) ;
在Windows中,使用MessageBox显示结果优於puts。
使用sprintf时,有一个问题,即定义的字串缓冲区必须足够大以存放结果。Microsoft专用函数_snprintf解决了这一问题,此函数引进了另一个参数,表示以字符计算的缓冲区大小。
vsprintf是sprintf的一个变形,它只有三个参数。vsprintf用於执行有多个参数的自订函数,类似printf格式。vsprintf的前两个参数与sprintf相同:一个用於保存结果的字符缓冲区和一个格式字串。第三个参数是指向格式化参数阵列的指针。实际上,该指针指向在堆栈中供函数调用的变量。va_list、va_start和va_end宏定义(在STDARG.H中定义)帮助我们处理堆栈指针。
使用vsprintf函数,sprintf函数可以这样编写:
int sprintf (char * szBuffer, const char * szFormat, ...)
{
int iReturn ;
va_list pArgs ;
va_start (pArgs, szFormat) ;
iReturn = vsprintf (szBuffer, szFormat, pArgs) ;
va_end (pArgs) ;
return iReturn ;
}
va_start宏定义将pArg设置为指向一个堆栈变量,该变量位址在堆栈参数szFormat的上面。
由於许多Windows早期程序使用了sprintf和vsprintf,最终导致Microsoft向Windows API中增添了两个相似的函数。Windows的wsprintf和wvsprintf函数在功能上与sprintf和vsprintf相同,但它们不能处理浮点格式。
当然,随著宽字符的发表,sprintf类型的函数增加许多,使得函数名称变得极为混乱。表2-1列出了Microsoft的C执行时期程序库和Windows支援的所有sprintf函数。
表2-1
ASCII
宽字符
常规
参数的变量个数
标准版
sprintf
swprintf
_stprintf
最大长度版
_snprintf
_snwprintf
_sntprintf
Windows版
wsprintfA
wsprintfW
wsprintf
参数阵列的指针
标准版
vsprintf
vswprintf
_vstprintf
最大长度版
_vsnprintf
_vsnwprintf
_vsntprintf
Windows版
wvsprintfA
wvsprintfW
wvsprintf
在宽字符版的sprintf函数中,将字串缓冲区定义为宽字串。在宽字符版的所有这些函数中,格式字串必须是宽字串。不过,您必须确保传递给这些函数的其他字串也必须由宽字符组成。
2.3.5 格式化讯息方块
程序2-1所示的SCRNSIZE程序展示了如何实现MessageBoxPrintf函数,该函数有许多参数并能像printf那样编排它们的格式。
程序2-1 SCRNSIZE
SCRNSIZE.C
/*---------------------------------------------------------------------------
SCRNSIZE.C -- Displays screen size in a message box
(c) Charles Petzold, 1998
----------------------------------------------------------------------------*/
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
int CDECL MessageBoxPrintf (TCHAR * szCaption, TCHAR * szFormat, ...)
{
TCHAR szBuffer [1024] ;
va_list pArgList ;
// The va_start macro (defined in STDARG.H) is usually equivalent to:
// pArgList = (char *) &szFormat + sizeof (szFormat) ;
va_start (pArgList, szFormat) ;
// The last argument to wvsprintf points to the arguments
_vsntprintf ( szBuffer, sizeof (szBuffer) / sizeof (TCHAR),
szFormat, pArgList) ;
// The va_end macro just zeroes out pArgList for no good reason
va_end (pArgList) ;
return MessageBox (NULL, szBuffer, szCaption, 0) ;
}
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
int cxScreen, cyScreen ;
cxScreen = GetSystemMetrics (SM_CXSCREEN) ;
cyScreen = GetSystemMetrics (SM_CYSCREEN) ;
MessageBoxPrintf ( TEXT ("ScrnSize"),
TEXT ("The screen is %i pixels wide by %i pixels high."),
cxScreen, cyScreen) ;
return 0 ;
}
经由从GetSystemMetrics函数得到的信息,该程序以像素为单位显示了视讯显示的宽度和高度。GetSystemMetrics是一个能用来获得Windows中不同物件的尺寸信息的函数。
2.3.6 本书与国际化