Unicode与国际化软件开发学习心得

11 篇文章 0 订阅

 “每一位软件开发人员必须、绝对要至少具备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中提供了很多辅助工具函数:
MultiByteToWideChar
WideCharToMultiByte
IsDBCSLeadByte(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字节,字符串长度5
WCHAR *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 _UNICODE
typedefwchar_t     TCHAR;
#else
typedefchar            TCHAR;
#endif
•根据_UNICODE宏定义确定char还是WCHAR
•TCHAR以外,相关的数据类型LPTSTR, LPCTSTR也具有类似的情况。
•双编译解决方案——常量数据
宏 _T()和宏 L
依赖于_UNICODE宏
•双编译解决方案——C运行时库函数
依赖于_UNICODE宏
             wchar.h       tchar.h
strxxx,   wcsxxx   -> _tcsxxx
printf,    wprintf    -> _tprintf
xxx,       wxxx      -> _txxx
xxx,       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,否则容易引起冲突

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值