Unicode介绍
U n i c o d e是A p p l e和X e r o x公司于1 9 8 8年建立的一个技术标准。1 9 9 1年,成立了一个集团机
构负责U n i c o d e的开发和推广应用。
Windows 2000是使用U n i c o d e从头进行开发的,用于创建窗口、显示文本、进行字符串操
作等的所有核心函数都需要U n i c o d e字符串。通过从头开始用U n i c o d e来开发应用程序,
就能够使你的应用程序更加有效地运行。
为了使Windows CE尽量做得小一些,M i c r o s o f t公司决定完全不支持ANSI Wi n d o w s
函数。因此,如果要为Windows CE开发应用程序,必须懂得U n i c o d e,并且在整个应用程序中
使用U n i c o d e。
当M i c r o s o f t公司将C O M从1 6位Wi n d o w s转换成Wi n 3 2时,公司作出了一个决定,即需要字
符串的所有C O M接口方法都只能接受U n i c o d e字符串。
如何编写U n i c o d e源代码
M i c r o s o f t公司为U n i c o d e设计了Windows API,这样,可以尽量减少对你的代码的影响。实
际上,你可以编写单个源代码文件,以便使用或者不使用U n i c o d e来对它进行编译。只需要定
义两个宏(U N I C O D E和_ U N I C O D E),就可以修改然后重新编译该源文件。
C运行期库对U n i c o d e的支持
为了利用U n i c o d e字符串,定义了一些数据类型。标准的C头文件S t r i n g . h已经作了修改,
以便定义一个名字为w c h a r _ t的数据类型,它是一个U n i c o d e字符的数据类型:
typedef unsigned short wchar_t ;
wchar_t wcscat(wchar_t * , const wchar_t *);
wchar_t * wcschr(wchar_t * , wchar_t );
wchar_t * wcscpy(wchar_t * , const wchar_t * );
size_t wcslen (const wchar_t * );
前面说过,可以创建同时为A N S I和U n i c o d e进行编译的单
个源代码文件。若要建立双重功能,必须包含T C h a r. h文件,而不是包含S t r i n g . h文件。
T C h a r. h文件的唯一作用是帮助创建A N S I / U n i c o d e通用源代码文件。它包含你应该用在源
代码中的一组宏,而不应该直接调用s t r函数或者w c s函数。如果在编译源代码文件时定义了
_ U N I C O D E,这些宏就会引用w c s这组函数。如果没有定义_ U N I C O D E,那么这些宏将引用s t r
这组宏。
_UNICODE 和 _TEXT 定义
若要定义一个A N S I / U n i c o d e通用的字符串数组,请使用下面的T C H A R数据类型。如果定
义了_ U N I C O D E,T C H A R将声明为下面的形式:
typedef wchar_t TCHAR ;
如果没有定义_ U N I C O D E,则T C H A R将声明为下面的形式:
typedef char TCHAR ;
若要生成一个U n i c o d e字符串而不是A N S I字符串,必须将该代码行写为下面的样
子:
TCHAR szString []= L"error"; //字符串前面的大写字母L,用于告诉编译器该字符串作为Unicode字符串来编译.
因此我们需要另一个宏,以便有选择地在字符串的前面加上大写字母L。这项工作由_ T E X T宏来完成,
_ T E X T宏也在T C h a r. h文件中做了定义。如果定义了_ U N I C O D E,那么_ T E X T定义为下面的形
式:
#define _TEXT(x) L ## x
如果没有定义_ U N I C O D E,_ T E X T将定义为:
#define _TEXT(x) x
使用该宏,可以改写上面这行代码,这样,无论是否定义了_ U N I C O D E宏,它都能够正确
地进行编译。如下所示:
TCHAR szString []= _TEXT("error");
单个字符定义为:
TCHAR trTmp = _TEXT('J');
Wi n d o w s定义的U n i c o d e数据类型
WCHAR Unicode字符
PWSTR 指向Unicode字符串的指针
PCWSTR 指向一个恒定的Unicode字符串指针
这些数据类型是指U n i c o d e字符和字符串。Wi n d o w s头文件也定义了A N S I / U n i c o d e通用数
据类型P T S T R和P C T S T R。这些数据类型既可以指A N S I字符串,也可以指U n i c o d e字符串,这
取决于当编译程序模块时是否定义了U N I C O D E宏。
请注意,这里的U N I C O D E宏没有前置的下划线。_ U N I C O D E宏用于C运行期头文件,而
U N I C O D E宏则用于Wi n d o w s头文件。当编译源代码模块时,通常必须同时定义这两个宏。
如果要创建其他软件开发人员将要使用的动态链接库( D L L),请考虑使用下面的方法。
在D L L中提供两个输出函数。一个是A N S I版本,另一个是U n i c o d e版本。在A N S I版本中,只需
要分配内存,执行必要的字符串转换,并调用该函数的U n i c o d e版本(本章后面部分介绍这个
进程)。
在用动态链接库分配的头文件中,可以像下面这样建立这两个函数的原型
BOOL StringReverseW ( PWSTR pWideCharStr );
BOOL StringReverseA ( PSTR pMultiByteStr );
#ifdef UNICODE
#define StringReverse StringReverseW
#else
#define StringReverse StringReverseA
#endif // ! UNICODE
Wi n d o w s字符串函数
Wi n d o w s还提供了一组范围很广的字符串操作函数。这些函数与C运行期字符串函数(如
s t r c p y和w c s c p y)很相似。但是该操作系统函数是操作系统的一个组成部分,操作系统的许多
组件都使用这些函数,而不使用C运行期库。建议最好使用操作系统函数,而不要使用C运行
期字符串函数。
它的形式类似这个样子: S t r C a t、S t r C h r、S t r C m p和S t r C p y等。若要使用这些函数,必须加上
S h l WA p i . h头文件。另外,如前所述,这些字符串函数既有A N S I版本,也有U n i c o d e版本,例
如S t r C a t A和S t r C a t W。由于这些函数属于操作系统函数,因此,当创建应用程序时,如果定义
了U N I C O D E(不带前置下划线),那么它们的符号将扩展为宽字符版本。
成为符合A N S I和U n i c o d e的应用程序
下面是应该遵循的一些基本原则:
• 将文本串视为字符数组,而不是c h a r s数组或字节数组。
• 将通用数据类型(如T C H A R和P T S T R)用于文本字符和字符串。
• 将显式数据类型(如B Y T E和P B Y T E)用于字节、字节指针和数据缓存。
• 将T E X T宏用于原义字符和字符串。
• 执行全局性替换(例如用P T S T R替换P S T R)。
• 修改字符串运算问题。例如函数通常希望你在字符中传递一个缓存的大小,而不是字节。
这意味着你不应该传递s i z e o f ( s z B u ff e r ) ,而应该传递( s i z e o f ( s z B u ff e r ) / s i z e o f ( T C H A R )。另外,如果需要为字符串分配一个内存块,并且拥有该字符串中的字符数目,那么请记住要按字节来
分配内存。这就是说,应该调用malloc(nCharacters *sizeof(TCHAR)), 而不是调用m a l l o c
( n C h a r a c t e r s )
增强型sprintf和swprintf函数(请注意大写S和小写s的使用)
char szA[100];
WCHAR szW[100];
sprintf ( szA , "%s" , "ANSI STR");
sprintf ( szA , "%S" , L"Unicode STR");
swprintf ( szW, L"%s" , L"Unicode STR" );
swprintf ( szW , L"%S", "ANSI STR");
在U n i c o d e与A N S I之间转换字符串
int MultiByteToWideChar (
UINT uCodePage,
DWORD dwFlags,
PCSTR pMultiByteStr,
int cchMultiByte,
PWSTR pWideCharStr,
int cchWideChar );
u C o d e P a g e参数用于标识一个与多字节字符串相关的代码页号。d w F l a g s参数用于设定另一
个控件,它可以用重音符号之类的区分标记来影响字符。这些标志通常并不使用,在d w F l a g s
参数中传递0。p M u l t i B y t e S t r参数用于设定要转换的字符串, c c h M u l t i B y t e参数用于指明该字符
串的长度(按字节计算)。如果为c c h M u l t i B y t e参数传递- 1,那么该函数用于确定源字符串的长
度。
转换后产生的U n i c o d e版本字符串将被写入内存中的缓存,其地址由p Wi d e C h a r S t r参数指
定。必须在c c h Wi d e C h a r参数中设定该缓存的最大值(以字符为计量单位)。如果调用
M u l t i B y t e To Wi d e C h a r,给c c h Wi d e C h a r参数传递0,那么该参数将不执行字符串的转换,而是
返回为使转换取得成功所需要的缓存的值。一般来说,可以通过下列步骤将多字节字符串转换
成U n i c o d e等价字符串:
1) 调用M u l t i B y t e To Wi d e C h a r函数,为p Wi d e C h a r S t r参数传递N U L L,为c c h Wi d e C h a r参数
传递0。
2) 分配足够的内存块,用于存放转换后的U n i c o d e字符串。该内存块的大小由前面对M u l t B y t e To Wi d e C h a r的调用返回。
3) 再次调用M u l t i B y t e To Wi d e C h a r,这次将缓存的地址作为p Wi d e C h a r S t r参数来传递,并
传递第一次调用M u l t i B y t e To Wi d e C h a r时返回的缓存大小,作为c c h Wi d e c h a r参数。
4. 使用转换后的字符串。
5) 释放U n i c o d e字符串占用的内存块。
int WideCharToMultiByte (
UINT uCodePage ,
DWORD dwFlags ,
PCWSTR pWideCharStr ,
int cchWideChar ,
PSTR pMultiByteStr ,
int cchMultiByte ,
PCSTR pDefaultChar ,
PBOOL pfUsedDefaultChar );
p Wi d e C h a r S t r参数用于设定要转换的字符串的内存地址,c c h Wi d e C h a r参数用于指明该字符串
的长度(用字符数来计量)。如果你为c c h Wi d e C h a r参数传递- 1,那么该函数用于确定源字符串的
长度。
转换产生的多字节版本的字符串被写入由p M u l t i B y t e S t r参数指明的缓存。必须在c c h M u l t i B y t e
参数中设定该缓存的最大值(用字节来计量)。如果传递0作为Wi d e C h a r To M u l t i B y t e函数的
c c h M u l t i B y t e参数,那么该函数将返回目标缓存需要的大小值。
你会发现,Wi d e C h a r To M u l t i B y t e函数接受的参数比M u l t i B y t e To Wi d e C h a r函数要多2个,
即p D e f a u l t C h a r和p f U s e d D e f a u l t C h a r。只有当Wi d e C h a r To M u l t i B y t e函数遇到一个宽字节字符,
而该字符在u C o d e P a g e参数标识的代码页中并没有它的表示法时,Wi d e C h a r To M u l t i B y t e函数才
使用这两个参数。如果宽字节字符不能被转换,该函数便使用p D e f a u l t C h a r参数指向的字符。
如果该参数是N U L L(这是大多数情况下的参数值),那么该函数使用系统的默认字符。该默
认字符通常是个问号。这对于文件名来说是危险的,因为问号是个通配符。
p f U s e d D e f a u l t C h a r参数指向一个布尔变量,如果宽字符串中至少有一个字符不能转换成等
价多字节字符,那么函数就将该变量置为T R U E。如果所有字符均被成功地转换,那么该函数
就将该变量置为FA L S E。当函数返回以便检查宽字节字符串是否被成功地转换后,可以测试该
变量。同样,通常为该测试传递N U L L。