“每一位软件开发人员必须、绝对要至少具备Unicode与字符集知识(没有任何例外!)”
Windows系统的Unicode特性由于历史的原因,计算机系统中存在大量的ANSI特性,其使用的大多数文本文件,比如.txt,.ini,.cpp,.xml等等,大都是基于ANSI/DBCS编码的文件但是,从Windows2K开始,从Windows系统的底层实现,已经全面基于Unicode,同时仍保证完全兼容ANSI/DBCS程序。虽然大多数应用程序仍是基于ANSI/DBCS,但是目前越来越多的程序支持,或者基于Unicode规范。基于Unicode开发应用软件,开发效率更高,程序更通用,生成的数据也可以打破语言平台的限制。因此,我们认为,Unicode是Windows软件开发的大趋势。Window NT 操作系统的基本文本表示是UTF-16,WCHAR是其基本数据类型。
Windows系统代码页对于任何语言版本的Windows,系统都需要一个默认代码页,表示当前使用何种DBCS/扩展ASCII的文字编码。西欧: CP1252简体中文系统: CP936繁体中文系统: CP950日文系统: CP932韩文系统: CP949当前系统代码页数值可以通过Windows API函数GetACP()得到。该默认代码页可以手动修改。
修改Windows默认代码页ANSI函数和Unicode函数现有的Windows操作系统,包括98/2K/XP,其API接口中均存在两套函数,一套称为ANSI版本,支持ANSI/DBCS字符串,另一套称为Unicode版本,支持Unicode字符串。例如,Kernel32.dll中,对于DeleteFile函数,使用Depends工具可以看出,实际上在接口中同时存在DeleteFileA和DeleteFileW两个函数,可供程序员调用。这类对应的函数对,在功能方面完全相同,仅仅是输入字符串参数和返回值的类型定义不同,一为char*,一为WCHAR*。针对ANSI字符串,Windows在其API中提供了很多辅助工具函数:MultiByteToWideCharWideCharToMultiByteIsDBCSLeadByte(IsDBCSLeadByteEx)CharNext(CharNextExA)CharPrev(CharPrevExA)
VC开发中MBCS/Unicode字符串转换两个重要转化函数,用于处理Unicode字符和不同代码页字符串之间的转换MultiByteToWideChar 和 WideCharToMultiByte这两个函数参数比较多,用起来不是很方便,但是非常重要!大家一定要掌握。下面以MultiByteToWideChar为例:int MultiByteToWideChar( UINT CodePage, // 前面提过的代码页,Unicode与MBCS之间的桥梁,指定MBCS编码 DWORD dwFlags, // 不用太注意,设默认值 1就好 LPCSTR lpMultiByteStr, // 源字符串 int cbMultiByte, // 源字符串长度,建议明示给出,或者给-1,则自动寻找“/0”结尾 LPWSTR lpWideCharStr, // 目标字符串,应事先开好内存缓冲区 int cchWideChar // 目标缓冲区大小,如果是0,则计算该字符串转化后实际的字符个数 // 作为函数返回值,不进行真正的转换); ANSI程序与Unicode程序虽然ANSI函数和Unicode函数可以在同一个应用程序中混合使用,但是通常为了避免混乱,在绝大多数情况下,我们在同一个应用程序中,只会使用一套函数。使用ANSI函数的应用程序,我们称为ANSI程序;使用Unicode函数的应用程序,我们称为Unicode程序。在这样的程序中,所有的字符串都以WCHAR为基本单元,而非char。
使用VC++6.0开发Unicode程序VC++6.0是Windows应用软件开发的最佳工具之一,也非常适合编写Unicode程序。编写Unicode程序本身,并不是非常的困难,但是对于程序员在编程基本功方面的要求还是比较高的。需要程序员对Windows API,C++语言,VC++工具,文字Unicode编码等多方面都要有比较全面的知识以及深入的理解。现有的ANSI程序向Unicode转化,也是一种比较常见的工作,难度并不大,但是需要耐心细致的处理每一行代码,以及相应的调试能力。理解Unicode字符串在标准C语言中,字符串使用char*表示。我们现在可以知道,正确的说法是:在C/C++语言中,char*表示ANSI/DBCS字符串;而Unicode字符串用WCHAR*表示。例如:char *szText = “abc啊”; // DBCS字符串,占6字节,字符串长度5WCHAR *szuText = L“abc啊”; // Unicode字符串,10字节,字符串长度4其中, WCHAR是一种数据类型,定义如下: typedef unsigned short WCHAR;L是一个宏,表示其后的字符串常量是一个Unicode类型的字符串。
Unicode字符串函数ANSI C中有一系列ANSI字符串处理函数,在VC++提供的C Runtime库中,除了这些函数外,还提供了对应的Unicode函数。Windows API函数的Unicode版本多数Windows API函数都会具有字符串类型的参数或者返回值。这些函数中,绝大多数都同时存在ANSI版本和Unicode版本。例如:CreatProcessA / CreatProcessW极少数的例外,如:GetProcAddress 几点注意区分Windows API函数和C Runtime Library (C运行时库)。二者本身形式不同,ANSI/Unicode转换方式也不同。理解字符串长度和字符串内存占用的区别,简单说,就是字符数和字节数的区别。sizeof : 字节数strlen/wcslen: 字符数ANSI条件下, 字节数 = 字节数 + 1 // 因为“/0”Unicode条件下, 字节数 = (字符数 + 1) * 2通用公式, 字节数 = (字符数 + 1) * sizeof(TCHAR)
•创建Unicode应用程序步骤
•使用MFC向导生成应用程序框架。
•(可选)以“Debug”和“Release”为模版,增加2个Configuration,可分别称为“Unicode Debug”和“Unicode Release”。
•在“Project->Settings->C/C++->General->Preprocess definitions” 编辑框中,去掉“_MBCS”,增加“UNICODE, _UNICODE”。
•在“Project->Settings->Links->Output->Entry-point symbol”编辑框中,填写“wWinMainCRTStartup”。
•编译运行,可用Spy++查看view窗口的类名
•问题•由于Unicode函数与ANSI函数数据类型完全不兼容,通常不能直接混用。给开发Unicode应用程序带来一定的困难,所有的程序代码都要改写,而且修改过程几乎是一个不可逆的过程。•人们希望有一种方法,使用同样的代码,通过编译选项,控制得到的二进制应用程序是Unicode版本还是ANSI版本。•微软在设计操作系统、SDK、VC++、MFC等产品时,充分考虑到了这一点,也为我们提供了一个相当不错的解决方案。•双编译解决方案——数据类型•TCHAR#ifdef _UNICODEtypedefwchar_t TCHAR;#elsetypedefchar TCHAR;#endif•根据_UNICODE宏定义确定char还是WCHAR•TCHAR以外,相关的数据类型LPTSTR, LPCTSTR也具有类似的情况。•双编译解决方案——常量数据宏 _T()和宏 L依赖于_UNICODE宏•双编译解决方案——C运行时库函数依赖于_UNICODE宏 wchar.h tchar.hstrxxx, wcsxxx -> _tcsxxxprintf, wprintf -> _tprintfxxx, wxxx -> _txxxxxx, xwxx -> _xtxx
•双编译解决方案——WindowAPI函数考虑Windows API函数 LoadLibrary:MSDN文档:HMODULE LoadLibrary( LPCTSTR lpFileName); winbase.h中的定义:WINBASEAPI HMODULE WINAPI LoadLibraryA( LPCSTR lpLibFileName);WINBASEAPI HMODULE WINAPI LoadLibraryW(LPCWSTR lpLibFileName);#ifdef UNICODE#define LoadLibrary LoadLibraryW#else#define LoadLibrary LoadLibraryA#endif // !UNICODE•双编译解决方案——WindowAPI函数(续)•因此,我们可以在程序中直接写函数的名称,而不用考虑后缀A或W。在编译时,编译器会根据UNICODE宏定义的情况自动翻译成为所需要的函数名。•双编译解决方案——MFC类库•MFC类库中的大多数类都自动支持双编译,也就是说,在这些类中,成员变量中字符串类型都是T系列,成员函数的参数和返回值也都是T系列。•CString就是其中双编译特性最典型的。
•注意•强烈建议UNICODE宏和_UNICODE宏同时定义或同时无定义,否则容易引起混乱。•C++是强类型的语言,当字符串类型不匹配时会有错误或者警告,但C语言则未必,在进行C语言程序Unicode 改造时要格外注意。•sizeof函数与_tcslen函数的逻辑意义。最最容易出错的地方!切记!
•资源与国际化软件开发•国际化软件产品,是指软件产品具有可以在多种语言环境下安装、使用的单一二进制应用程序。–全球化 Globalization(编程、设计、工程化)–本地化 Localization(翻译、定制、技术文档撰写)•单一二进制(Single Binary) + 资源dll•概念:完全全球化后的功能二进制文件,不用再作修改就可用于该软件的任何一种语言版本。•单一二进制(Single Binary) + 资源dll•开发过程中,将主程序中语言相关的资源部分与主程序分离,制作成为资源dll,剩下的语言无关的主程序称为Single Binary。•其他语言版本的本地化过程,仅仅是界面元素的翻译,可由第三方完成,不需将整个程序的代码全部交出,即可由翻译机构对资源dll进行编译测试。既可缩短工作周期,又可保证代码安全。•主程序在运行时,可以按照用户的指定,动态地决定加载并使用何种语言的资源dll;也可以按照系统默认值,在不同版本中加载相应的不同的资源dll。这样,就可以实现按照资源dll中的语言内容显示用户界面。
•注意事项•所有语言相关的资源都应与代码分离,制作专门的资源dll模块。•将.rc文件及所有相关的图标、光标、bmp等•不在代码中硬编码语言相关的字符串,所有字符串都从资源中通过LoadString获得。•各种语言的资源dll,应尽量与主程序共享相同的resource.h,以及语言无关的图标、光标、bmp,防止版本混乱。•资源dll建立–将主程序中的rc文件复制(数目根据目标语言的数目),以明显的文件名命名,如chs.rc/eng.rc等等。–在主程序目录下,新建win32 dll工程(不必使用MFC),以明显语言名命名如res_chs。–将相应语言rc文件以及公用的resource.h加入该dll工程。–链接选项中加入命令“/noentry”。–编译链接,生成资源dll。•资源dll使用–静态切换(程序启动时切换一次),在程序初始化时,添加下列程序:•加载指定语言的资源dll HINSTANCE hRes = LoadLibrary(“res_chs.dll”);•指定该模块为程序默认资源句柄 AfxSetResourceHandle(hRes);–动态切换(程序运行后按用户要求随时切换),基本原理与静态切换类似,有几点需要注意•LoadLibrary函数,需要对应有FreeLibrary,否则会有泄漏•如果系统存在主菜单,需要将主菜单重新生成•多个资源dll最好进行rebase,否则容易引起冲突