




已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
转 谈谈Windows程序中的字符编码转谈谈Windows程序中的字符编码2011-05-26 16:32写这篇文章的起因是这么一个问题:我们在使用和安装Windows程序时,有时会看到以2052、1033这些数字为名的文件夹,这些数字似乎和字符集有关,但它们究竟是什么意思呢?研究这个问题的同时,又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间或乐趣点缀的旅程。0 Where is Win32 API Windows程序有用户态和核心态的说法。在32位地址空间中,0x 80000000以下属于用户态,0x 80000000以上属于核心态。所有硬件管理都在核心态。用户态程序的不能直接使用核心态的任何代码。所谓核心态其实只是CPU的一种保护模式。在x86 CPU上,用户态处于ring 3,核心态处于ring 0。从用户态进入核心态的最常用的方法是在寄存器eax填一个功能码,然后执行int 2e。这有点像DOS时代的DOS和BIOS系统调用。在NT架构中这种机制被称作system service。在核心态提供system service的有两个家伙:ntoskrnl.exe和win32k.sys。ntoskrnl.exe是Windows的大脑,它的上层被称为Executive,下层被称作Kernel。Win32k.sys提供与显示有关的system service。在用户态一侧,有一个重要的角色叫作ntdll.dll,大多数system service都是它调用的。它封装这些system service,然后提供一个API接口。这个接口被称作native API。native API的用户是各个子系统(subsystem),包括Win32子系统、OS/2子系统、POSIX子系统。各个子系统为Win32、OS2、POSIX程序提供了运行平台。ntdll.dll由于提供了平台无关的API接口,所以被看作是NT系统的原生接口,由之得到了native API的匪号。其实它的主要工作是将调用传递到核心态。Win32、OS/2、POSIX,听起来很庞大。其实真正做好的只有Win32子系统。OS2、POSIX都是Console UI,即只有字符界面。提供OS/2子系统,只因为在1988年,NT的主要设计目标就是与OS/2兼容,后来由于Windows 3.0卖得很好,所以设计目标被变更为与Windows兼容。提供POSIX子系统,是为了应付美国政府的一个编号为FIPS 151-2的标准。Win32子系统的管理员是一个叫作csrss.exe的弟兄,它的全名是:Client/Server Run-Time Subsystem。它刚上任时,本来要分管所有的子系统,但后来POSIX和OS/2都被分别处理了,所以只管了一个Win32。即使这样也很了不起,所有的Win32程序的进程、线程们都要向它登记。不过Win32程序用得最多的还是Win32子系统的DLL们,最核心的DLL包括:kernel32.dll、User32.dll、Gdi32.dll、Advapi32.dll。这些DLL包装了ntdll.dll的native API。其中Gdi32.dll比较特殊,它与核心态的win32k.sys直接保持联系,以提高NT系统的图形处理能力。Win32子系统的DLL们提供的接口函数在MSDN文档中被详细介绍,它们就是Win32 API。附录0 Windows的启动计算机上电后,从BIOS的ROM开始运行。BIOS在做一些初始化后会将硬盘的第一个扇区的数据读入内存,然后将控制权交给它,这段数据被称作Master Boot Record(MBR)。MBR包含一段启动代码和硬盘的主分区表。这段启动代码扫描主分区表,找到第一个可以启动的分区,然后将这个分区的第一个扇区读入内存并运行。这个扇区被称作引导扇区(boot sector)。引导扇区的代码具备读文件系统根目录的能力,显然不同的文件系统需要不同的代码。引导扇区会从根目录中读出一个叫作ntldr的文件。顾名思义,这个文件是load NT的主要角色。它的业绩主要包括将CPU从实模式转入保护模式,启动分页机制,处理boot.ini等。如果boot.ini中有一句:C:bootsect.rh=Red Hat Linuxbootsect.rh的内容是Linux引导扇区,用户又选择了Red Hat Linux,ntldr就会将执行Linux的引导扇区,开始Linux的引导。如果用户选择继续使用Windows,ntldr会装载并运行我们前面提到的ntoskrnl.exe。ntoskrnl.exe会启动会话管理器smss.exe。smss.exe启动csrss.exe和winlogon.exe。smss.exe会永远等待csrss.exe和winlogon.exe返回。如果两者之一异常中止,就会导致系统崩溃。所以病毒们经常以打击csrss.exe为乐。winlogon.exe负责用户登录,在完成登录后,它会启动注册表HKLMSOFTWAREMicrosoftWindows NTCurrent VersionWinlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置,让硬盘响个不停,并考验我们的耐性,最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后,我们就会看到熟悉的开始菜单和桌面。要了解Win32子系统的DLL们提供了哪些API,最直接的方法就是用Win32dsm直接查看DLL们的导出表。这时我们会发现Win32 API中带字符串的API一般都有两个版本,例如CreateFileA和CreateFileW。当然也有例外,例如GetProcAddress函数。A代表ANSI代码页,W是宽字符,即Unicode字符。Windows中的Unicode字符一般指UCS2的UTF16-LE编码。让我们通过几个实例观察A/W版本间的关系。例1:用WIn32dsm查看gdi32.dll的汇编代码,可以看到TextOutA调用GdiGetCodePage获取当前代码页,再调用MultiByteToWideChar转换输入的字符串,然后调用一个内部函数。而TextOutW直接调用这个内部函数。例2:用调试器跟踪一个使用了CreateFileA的程序,可以看到:CreateFileA在将输入字符串转换为Unicode后,会调用CreateFileW。假设输入文件名是测试.txt,对应的数据就是:B2 E2 CA D4 2E 74 78 74 00。在调试器中可以看到传给CreateFileW的文件名数据是:4B 6D D5 8B 2E 00 74 00 78 00 74 00 00 00。这是测试.txt对应的Unicdoe字符串。CreateFileW会接着调用ntdll.dll中的NtCreateFile。顺便看看NtCreateFile的代码:mov eax,00000020 lea edx,dword ptresp+04int 2E ret 002C可见这个native API只是简单地调用了核心态提供的0x20号system service。例3:gdi32.dll中的GetGlyphOutline函数可以获取指定字符的字模。GetGlyphOutlineA和GetGlyphOutlineW函数都会调用同一个内部函数(记作F)。函数F在返回前将通过int 2E调用0x10B1号system service。GetGlyphOutlineW直接调用函数F。GetGlyphOutlineA在调用函数F前,要依次调用GdiGetCodePage、IsDBCSLeadByteEx和MultiByteToWideChar,将当前代码页的字符编码转换成Unicode编码。如果我们调用GetGlyphOutlineA时传入baba,这是汉字的GBK编码,用调试器可以看到传给函数F的字符编码是6c49,这是汉字的Unicode编码。从以上例子可见,A版本总会在某处将输入的字符串转换为Unicode字符串,然后和W版本执行相同的代码。在由A/W版本API引出MBCS程序和Unicode程序前,让我们先解释一下Locale和ANSI代码页。2 Locale和ANSI代码页2.1 Locale和LCID Locale是指特定于某个国家或地区的一组设定,包括字符集,数字、货币、时间和日期的格式等。在Windows中,每个Locale可以用一个32位数字表示,记作LCID。在winnt.h中可以看到LCID的组成。它的高16位表示字符的排序方法,一般为0。在它的低16位中,低10位是primary language的ID,高4位指定sublanguage。sublanguage被用来区分同一种语言的不同编码。下面是部分primary language和sublanguage的常数定义:#define LANG_CHINESE 0x04#define LANG_ENGLISH 0x09#define LANG_FRENCH 0x0c#define LANG_GERMAN 0x07#define SUBLANG_CHINESE_TRADITIONAL 0x01/Chinese(Taiwan Region)#define SUBLANG_CHINESE_SIMPLIFIED 0x02/Chinese(PR China)#define SUBLANG_ENGLISH_US 0x01/English(USA)#define SUBLANG_ENGLISH_UK 0x02/English(UK)好,现在我们可以计算简体中文的LCID了,将sublanguage的常数左移10位,即乘上1024,再加上primary language的常数:2*1024+4=2052,16进制是0804。美国英语是:1*1024+9=1033,16进制是0409。繁体中文是1*1024+4=1028,16进制是0404。2.2代码页每个Locale都联系着很多信息,可以通过GetLocalInfo函数读取。其中最重要的信息就是字符集了,即Locale对应的语言文字的编码。Windows将字符集称作代码页。每个Locale可以对应一个ANSI代码页和一个OEM代码页。Win32 API使用ANSI代码页,底层设备使用OEM代码页,两者可以相互映射。例如English(US)的ANSI和OEM代码页分别为1252(ANSI-Latin I)和437(OEM-United States)。Chinese(PRC)的ANSI和OEM代码页都是950(ANSI/OEM-Traditional Chinese Big5)。Chinese(TW)的ANSI和OEM代码页都是936(ANSI/OEM-Simplified Chinese GBK)。附录1中有一张很长的表。列出了我正在使用的Windows所支持的135个Locale的部分信息,包括LCID、国家/地区名称、语言名称、语言缩写和对应的ANSI代码页。2.3系统Locale、用户Locale,再谈ANSI代码页在Windows中,通过控制面板可以为系统和用户分别设置Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。这不是一个好的设计,后面会谈到它带来的问题。使用GetSystemDefaultLCID函数和GetUserDefaultLCID函数分别得到系统和用户的LCID。有很多材料将这两个函数和另外两个函数混淆:GetSystemDefaultUILanguage和GetUserDefaultUILanguage。GetSystemDefaultUILanguage和GetUserDefaultUILanguage得到的是您当前使用的Windows版本所带的UI资源的语言。用户程序缺省使用的代码页是当前系统Locale的ANSI代码页,可以称作ANSI编码,也就是A版本的Win32 API默认的字符编码。对于一个未指定编码方式的文本文件,Windows会按照ANSI编码解释。2.4 AppLocale如果一个文本文件采用BIG5编码,系统当前的ANSI代码页是GBK。打开这个文件,就会显示乱码。例如中文在BIG5中的编码是A4A4、A4E5,这两个编码在GBK中对应的字符是。这是日文的两个平假名。在Windows XP平台有一个AppLocale程序,可以以指定的语言运行非Unicode程序。用Win32dsm打开看一看,其实它只是在运行程序前设置了两个环境变量。我们可以用个批处理文件模仿一下:ECHO OFF SET _COMPAT_LAYER=#ApplicationLocale SET ApplocaleID=0404 start notepad.exe在简体中文平台,用这个批处理文件启动的记事本可以正确显示BIG5编码的文本文件。用它打开GBK编码的文本文件会怎么样?中文会被显示为笢恅。设置这两个环境变量会作用于当前进程和其子进程。Windows 2000平台不支持这个方法。3 MBCS程序和Unicode程序3.1与字符编码有关的编译参数让我们回到Win32 API。我们在程序中使用的Win32 API没有A/W后缀,Windows的头文件会根据编译参数UNICODE将没有后缀的函数名替换为A版本或W版本,例如:#ifdef UNICODE#define CreateFile CreateFileW#else#define CreateFile CreateFileA#endif CRunTime库(CRT)使用_UNICODE和_MBCS来区分三套字符串处理函数,分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen,猜猜以下函数返回几?strlen(VOIP网关);_mbslen(unsigned char*)VOIP网关);wcslen(LVOIP网关);答案是8、6、6。LANSI字符串通知编译器将ANSI字符串转换为Unicode字符串,这是VC+编译器提供的一个小甜点。不过我们应该用宏:_T(ANSI字符串)。_T宏只在我们定义了_UNICODE时才转换。这样同一套代码既可以编译MBCS版本,也可以编译Unicode版本。MFC用_UNICODE参数区分Unicode版本特有的代码,决定使用什么版本的导入库或静态库。3.2 Unicode程序、MBCS程序和多语言支持Unicode程序直接使用Unicode版本的CRT和Win32 API。Unicode程序的运行与当前的ANSI代码页没有关系。MBCS程序的运行依赖于ANSI代码页。如果设计者和使用者使用不同的代码页,就可能出现乱码。微软开发的程序大都是Unicode程序,不管我们怎样变换系统Locale,它们总能正常运行。使用VCL类库的Delphi程序都是MBCS程序。VCL框架在程序启动会调用GetThreadLocale获取当前用户的LCID,然后在当前目录查找对应的资源文件,命名规则是:程序名+.+语言缩写,语言缩写可以参见附录1。在找不到时才会使用EXE文件中的资源。不过如果系统LCID是English(United States),用户LCID是Chinese(PRC),由VCL产生的程序就会出现乱码。读者可以自己分析原因。为VCL程序做多语言版本。只要用Delphi自带的Resource DLL Wizard再做一个特定语言的资源DLL,原来的程序都不用改。不过很多程序员用其它组件做多语言版本,例如TsiLang。MBCS程序虽然也可以做成多语言版本,但它无法在同时显示不同代码页特有的字符,这时就必须使用Unicode程序了。VS.NET文档中有个多语言资源的例子:SatDLL。它只用Win32 API的例子,却用了VC7项目。我在学习时将它改成了VC6项目,并纠正了它的两个问题:1、用GetUserDefaultUILanguage读到的是Windows资源版本,不是当前用户设置的代码页。2、启动时没有使用资源DLL里的菜单。在我的个人主页(上可以下载修改过的SatDLL。这个程序说明了支持多语言资源的基本思路:将不同语言资源放到不同的DLL中,在程序启动时根据当前Locale装载对应的资源DLL。必要时动态切换资源。为了标记不同语言的资源,可以将它们放到不同的目录中,以LCID作为目录名,例如2052、1033。当然我们也可以用其它方法联系LCID和资源DLL。MFC程序可以在App类的InitInstance函数中用AfxSetResourceHandle函数设置资源DLL。在Delphi中动态切换资源可以参考Delphi Demo目录RichEdit项目的ReInit.pas。在读取当前设定时,建议用GetSystemDefaultLCID函数,因为系统Locale决定ANSI代码页。3.4资源和乱码通过检查可执行文件,我们可以确定VC和Delphi的资源编译器都以Unicode保存字符资源。在VC环境编辑资源时,我们会指定资源的代码页。编译器根据资源的代码页,将其转换到Unicode。Unicode程序直接使用以Unicode编码保存的资源。MBCS程序需要将Unicode资源先转换回当前ANSI代码页,然后再使用。如果资源中的Unicode字符串不能映射到当前代码页中的字符,就会出现?。例如Windows的标准对话框也会出现乱码。假设我们使用简体中文Windows,当前Locale是Chinese(TW),我们的程序是MBCS的,使用标准的打开文件对话框。因为在BIG5中没有开这个字,所以打开会被显示成打?。将程序编译成Unicode版本,就可以避免这个问题。如果字符不是保存在资源中,而是硬编码在程序中。然后开发者和用户使用不同的代码页,就会导致乱码。假设开发者的Locale是Chine
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 农业银行2025资阳市笔试英文行测高频题含答案
- 交通银行2025本溪市秋招笔试专业知识题专练及答案
- 建设银行2025九江市秋招无领导模拟题角色攻略
- 工商银行2025周口市秋招英文面试题库及高分回答
- 交通银行2025渭南市秋招笔试价值观测评题专练及答案
- 邮储银行2025乌兰察布市秋招无领导小组面试案例题库
- 建设银行2025黄南藏族自治州信息科技岗笔试题及答案
- 农业银行2025合肥市秋招笔试热点题型专练及答案
- 班组建安全和培训课件
- 中国银行2025松原市秋招笔试创新题型专练及答案
- 商业银行法律纠纷诉讼策略的运筹方法
- 电气自动化基础知识
- 医学优质课件《胎盘异常》
- 新生儿低钙血症
- 熔化和凝固 全国公开课一等奖
- 人工智能训练师基础(上册)
- 粘多糖贮积症专家讲座
- 教学课件 国际结算(第七版)苏宗祥
- 成都燃气公司招聘笔试题
- 某铁路站房钢筋工程技术交底
- SMM英国建筑工程标准计量规则中文版全套
评论
0/150
提交评论