Windows字符集的统一与转换

Windows字符集的统一与转换

 

一、字符集的历史渊源

Windows编程时经常会遇到编码转换的问题,一直以来让刚接触的人摸不着头脑。其实只要弄清Win32程序使用的字符编码方式就清楚了,图1展示了一个Win32控制台项目的属性中的字符集选项。这里有两个不同的字符集:一个是Unicode字符集,另一个就是多字节字符集MBCS(Multi-Byte Character System),即熟知的ANSI字符集。

1 Visual Studio Win32项目属性

或许有人和我一样对这么一群“凭空出现”的字符集既痛恨又好奇,痛恨的是为什么不使用统一的方式对字符编码,还要在程序中不断的转换。好奇的原因亦是如此,既然躲不过这些东西,我们就探究一下它们的渊源。

伴随着图形界面计算机的出现,字符集就应运而生了。要显示字符信息,就需要将之转换为二进制信息表示——编码。“可悲”的是计算机是美国人发明的,而英语就是26个英文字母和一些常用标点符号的组合,这些字符称为ASCLL字符集。它是使用1个字节的长度进行编码,也就是能表示256个不同的字符,实际上真正用到的可见字符不到128个。

对于欧美国家的语言字符,ASCLL尚能应付自如,可是随着计算机的发展和普及,伴随着中文、日文、韩文等语言的需求,256个字符远远不能表示所有的常用字符了。这时就需要对原本的ASCLL进行改进以表示更多的字符,最简单最实际的做法就是扩展字节。将128作为分水岭,小于128的字符还是使用正常的一个字节的ASCLL进行表示,保证了英文的兼容。把大于128的字符作为一个引导字节,来决定后边字符的编码的长度和内容。通过这种变长的灵活编码方式,使得这种编码支持了几乎常用的所有语言的字符集,例如我们常用的GB2312GBKGB18030等等。由于ASCLL起初是ANSI的标准字符集,因此这种变长编码方式称为ANSI的多字节字符集MBCS,也称为为ANSI字符集。

然而好景并不长,由于变长的字符编码一般都是由各个国家自行编码的,因此没有一个统一的标准。尤其是中文的编码,在中国大陆、香港、台湾的中文编码方式截然不同,这就给信息的共享带来了很大的困难,最明显的是早期港台的网页到大陆打开时在没有编码转换时就无法正常显示。为了解决这个问题,国际Unicode联盟提出了统一的Unicode编码方式。Unicode标准编码方式是使用2个字节编码, 16位编码可以表示65536个字符,即UTF-16,基本上能表示世界上所有语言常用的字符。但是对于非常用字符则不能表示完全,比如中国的汉字千变万化,光康熙字典收录的字就将近五万个。因此就出现UTF-32编码,它能表示65536*65536=4294967296个字符,足够表示世界上所有语言的字符了。另外,为了保持和ASCLL的兼容以及满足部分只能处理单字节的系统的需要,UTF-8的编码方式使用和MBCS的编码相似的方式进行编码,但是它不和任何一个MBCS编码兼容。

由上可见,多种字符集的出现并非人为,而是计算机发展历史的需要。既然无法改变历史,我们只能顺应历史潮流,学习并正常使用这些千变万化的字符集。

二、字符集的统一处理

回到文章开始提到的Windows程序中使用两种编码方式,我们的目的是明确这两种编码方式的使用区别和相互转化的方式。

首先看字符集使用的区别。

如果使用MBCS字符集一般这么写:

定义一个MBCS字符数组:char arr[LEN];或者CHAR arr[LEN];

定义一个MBCS字符指针char *p;或者LPSTR p;

定义一个MBCS常量字符串指针const char * cp;或者LPCSTR cp;

定义一个MBCS常量字符串cp=”Hello World!\n”;

如果使用Unicode字符集一般这么写:

定义一个Unicode字符数组:wchar_t arr[LEN];或者WCHAR arr[LEN];

定义一个Unicode字符指针wchar_t *p;或者LPWSTR p;

定义一个Unicode常量字符串指针const wchar_t * cp;或者LPCWSTR cp;

定义一个Unicode常量字符串cp=L”Hello World!\n”;

一般字符集和串操作离不开。

如果对MBCS字符串连接、复制、比较、求长运算为:strcatstrcpystrcmpstrlen

如果对Unicode字符串连接、复制、比较、求长运算为:wcscatwcscpywcscmpwcslen

类似的情况还有很多,那么这里就有很大的问题。如果源代码改变一下字符集的类型,那么源代码中所有和字符、串相关的函数、定义、声明都需要修改。不过这点早就被人考虑到了,Windows提供了头文件tchar.h来解决这些字符集通用的问题。它使用一个UNICODE宏来标识当前工程使用的字符集是MBCS还是Unicode。如果使用tchar如何书写上边的代码呢?

对于相应的字符集定义和串操作如下:

定义一个字符数组:TCHAR arr[LEN];

定义一个字符指针LPTSTR p;

定义一个常量字符串指针LPCTSTR cp;

定义一个常量字符串cp=_T(”Hello World!\n”);

       连接、复制、比较、求长运算为:_tcscat_tcscpy_tcscmp_tcslen

这里的TCHAR不是一个新的类型,它是根据UNICODE宏来自动映射为charwchar_t,相应的LPTSTRLPCTSTR_T()宏亦是如此。

将上述的宏定义抽象出来如下:

#ifdef UNICODE
typedef wchar_t WACHR,TCHAR;
typedef wchar_t *LPWSTR,*LPTSTR;
typedef const wchar_t *LPCWSTR,*LPCTSTR;
#define _T(x) L ## x
#define _tcscat wcscat
#define _tcscpy wcscpy
#define _tcscmp wcscmp
#define _tcslen wcslen
#else
typedef char CHAR,TCHAR
typedef char *LPSTR,*LPTSTR;
typedef const char *LPCSTR,*LPCTSTR;
#define _T(x) x
#define _tcscat strcat
#define _tcscpy strcpy
#define _tcscmp strcmp
#define _tcslen strlen
#endif

因此,使用TCHAR代替已有的字符、串定义、操作可以完成字符集处理的统一和通用化。

三、字符集的相互转换

然而事情并不是总是那么绝对,一个工程中很难保证所有的涉及字符集的地方都是使用的相同的字符集。在一个Unicode字符集的项目中使用MBCS的函数调用是常有的事情,例如系统API WinExec是执行一个Windows命令,它的第一个参数LPCSTR lpCmdLine标识了它只接收MBCS的字符串。为了满足这里“意外”的需求,必须来实现字符集间的相互转化。当然,Windows提供了这种转化方式,但是有多种方式:一种是使用系统提供的API WideCharToMultiByteMultiByteToWideChar,使用API转换参数较多,使用起来并不是很方便,但是转换结果比较稳定;另外C库提供一种简便的转换函数wcstombsmbstowcs,这两个函数参数简单,使用起来很方便,例如:     

WCHAR src[20]=L"wide 字符";
CHAR des[40];
WCHAR des2[20];
setlocale(LC_ALL,"");
wcstombs(des,src,20);
cout<<des<<endl;
mbstowcs(des2,des,40);
wcout<<des2<<endl;

这两个函数使用前需要使用setlocale设置本地化信息,否则转换后的字符串会出现中文乱码情况。对于中文Windows操作系统使用setlocale(LC_ALL,"")即可,否则使用命令chcp查看活动代码页。

对于中文操作系统结果为936,调用setlocale(LC_ALL,".936")也可以正常完成转化(注意936前的一个点符号)

通过以上的介绍,相信大家对Windows的字符集的使用和转换应该有了一个更清晰的了解,若有错误还望不吝指正!

posted @ 2012-08-28 17:28  Florian  阅读(11058)  评论(0编辑  收藏  举报