精华内容
下载资源
问答
  • 程序中的编码常识

    千次阅读 2018-10-25 10:44:34
    程序中的编码问题        相信很多小伙伴在开始学编程的时候就很奇怪一个问题,什么时候是ASCII编码,什么时候是Unicode码,什么时候是UTF-8编码…...

    程序中的编码问题

           相信很多小伙伴在开始学编程的时候就很奇怪一个问题,什么时候是ASCII编码,什么时候是Unicode码,什么时候是UTF-8编码…这些问题大多时候很多人可能都囫囵吞枣,不求甚解,不多说了,我也是这样,所以为了方便更多的小伙伴了解这个问题,我整理了一下程序中的编码知识。

    参考链接:字符与编码

    编码发展史

           在计算机发展之初,美国作为计算机发源地,制定了美国信息交换标准代码,也就是我们日常所说 的ASCII码(American Standard Code for Information Interchange),其中根据拉丁字母(拉丁字母(也称为罗马字母)是多数欧洲语言采用的字母系统,是世界上最通行的字母文字系统。)制定了一套标准,其中包含了有128个字符。
           可是随着计算机的发展,世界上其他很多国家也需要将自己的文字系统加入到计算机系统中,想一想,如果不将汉字加入进去,我们能够传输我们的中文信息吗?于是我们国家也制定了一套自己的编码系统:(来自维基百科GB2312

    GB 2312 或 GB 2312–80 是中华人民共和国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,通常简称GB,又称GB0,由中国国家标准总局发布,1981年5月1日实施。GB 2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。

    这个和GBK是啥关系啊?

           在我们国家制定了GB 2312编码后,后面有逐渐增加了一些字符,所以有了GBK(汉字内码扩展规范,其中K就是中文意思中的扩展之意),其后又为了扩大,制定了GB18030编码规范。
    China Encode
           同样的道理,其他国家也肯定制定了自己的编码规则,例如日本的Shift_JIS,韩国的Euc-kr等等,所以为了规避在网络世界里大家各自为政,不便交流的问题,所以产生了Unicode编码(百度百科):

    Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

           但是Unicode编码中规定了通常都是2个字节,生僻字占4个字节。要知道的是ASCII码中每个字符都是只占1个字节啊,这样就导致了一个问题是:我们都是用英文字符(通俗的说法)来写的代码,这样的话Unicode编码可就比ASCII码至少多了一倍的传输量,这样很不利于资源的节约,所以在此基础上UTF-8编码应运而生(维基百科):

    UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为邮箱、网页及其他存储或发送文字的应用中,优先采用的编码。

           UTF-8采用了16个字节来存贮字符,其中字母1个字节,汉字3个字节,生僻字4到6个字节。

    总结图:

    编码总结

    用处呢?

    现在计算机系统通用的字符编码工作方式:

    在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。

           用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件:
    在这里插入图片描述

           浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器:
    在这里插入图片描述

           所以现在知道为什么我们网页传输的时候通常都会看见utf-8了吧:
    Google

    展开全文
  • 谈谈Windows程序中的字符编码

    千次阅读 2016-11-08 16:05:22
    谈谈Windows程序中的字符编码 写这篇文章的起因是这么一个问题:我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹,这些数字似乎和字符集有关,但它们究竟是什么意思呢? 研究...

    原文地址:http://www.fmddlmyy.cn/text7.html

    谈谈Windows程序中的字符编码

    写这篇文章的起因是这么一个问题:我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹,这些数字似乎和字符集有关,但它们究竟是什么意思呢?

    研究这个问题的同时,又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间或乐趣点缀的旅程。

    0 Where is Win32 API

    Windows程序有用户态和核心态的说法。在32位地址空间中,用户态代码只能访问0x80000000以下空间(其实只是0x00010000-0x7FFEFFFF),核心态代码可以访问0x80000000以上空间。所有硬件管理都在核心态。用户态代码不能直接使用核心态的任何代码。所谓用户态、核心态其实只是不同的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 Linux"

    bootsect.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负责用户登录,在完成登录后,它会启动注册表HKLM\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置,让硬盘响个不停,并考验我们的耐性,最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后,我们就会看到熟悉的开始菜单和桌面。

    1 Win32 API的A/W函数

    要了解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 ptr [esp+04]
    int 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代码页都是“936 (ANSI/OEM - Simplified Chinese GBK)”。 Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM - Traditional Chinese Big5)”。

    附录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

    C RunTime库(CRT)使用_UNICODE和_MBCS来区分三套字符串处理函数,分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen ,猜猜以下函数返回几?

    strlen("VOIP网关");
    _mbslen((unsigned char *)"VOIP网关");
    wcslen(L"VOIP网关");

    答案是8、6、6。L"ANSI字符串"通知编译器将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里的菜单。

    在我的个人主页(http://www.fmddlmyy.cn)上可以下载修改过的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是Chinese (PRC),用户的Locale是English (US),程序中硬编码了字符串“文件”。 Chinese (PRC)的ANSI代码页是GBK,“文件”的编码“CE C4 BC FE”。English (US)的ANSI代码页是Latin I,用户按照Latin I编码去解释“CE C4 BC FE”,就会看到“???t”。

    回答我前面提过的一个问题:Delphi程序根据用户LCID转换资源中的字符串。如果用户LCID是Chinese (PRC),系统LCID是English (US)。那么资源中的Unicode字符串会被转换为GBK编码,然后按照Latin I显示,这时我们看到的就是类似“???t”的东东,不是??。

    既然资源是以Unicode保存的,MBCS程序如果不将其转换到ANSI代码页,而用W版本的函数直接显示,就不会产生乱码。例如MFC程序菜单里的中文,在English (US)的Locale也可以正常显示。不过这取决于各部分代码的具体实现,menu bar控件里的中文在English (US)的Locale会全部显示成??。

    进一步的参考资料

    本文的第0节和附录0主要参考了《Inside Windows 2000 Third Edition》,国内出过该书的影印版。DDK文档中有大量Windows内核的信息。用Win32dsm和各种调试器查看Windows系统文件可以获得更直接的信息。

    关于Window程序的字符编码,最好的参考资料是winnt.h等SDK的包含文件、VCL、MFC、CRT的源文件。我们不需要阅读它们,只要找到自己感兴趣的信息就可以了,用Source Insight可能方便一些。

    本文所谈的不是什么万古不迁的道理,只是别的程序员的一些设定,我们因为需要使用他们的程序,所以有必要了解一些细节。研究问题的方法和兴趣永远比问题本身重要,如一句拉丁俗语所说:res, non verba,实质胜于文字。

    尾声

    “明月虽有圆缺,但毕竟永恒不灭,人生却如过眼烟云,一去不回,真不知计较为何?”

    “蛙声虽是短促,但却是万籁中一个活泼的禅机,也可以说万古如斯,永恒不迁,无奈感受到的,能有几人?”

    这是一本武侠书中的对话。在时间的长河中,人生和蛙声一样易逝。说到蛙声,我的20个月的小宝宝在喝汤后,略加酝酿,就会紧闭着嘴巴,发出很像蛙鸣的声音。我们会逗他说:“小青蛙又来了”。小家伙益发得意,不管我的抗议,将连汤带油的小下巴亲热地贴在我的身上。

     

    附录1 一些关于LCID的信息

    使用EnumSystemLocales函数可以枚举系统支持的LCID。用GetLocaleInfo可以得到ANSI代码页的ID,再通过GetCPInfoEx可以获得代码页的全称。以下是我在中文Windows XP上读到的内容。

    LCID

    国家或地区

    语言

    语言缩写

    ANSI代码页

    1025

    沙特阿拉伯

    阿拉伯语(沙特阿拉伯)

    ARA

    1256  (ANSI - 阿拉伯文)

    1026

    保加利亚

    保加利亚语

    BGR

    1251  (ANSI - 西里尔文)

    1027

    西班牙

    加泰隆语

    CAT

    1252  (ANSI - 拉丁文 I)

    1028

    台湾

    中文(台湾)

    CHT

    950   (ANSI/OEM - 繁体中文 Big5)

    1029

    捷克共和国

    捷克语

    CSY

    1250  (ANSI - 中欧)

    1030

    丹麦

    丹麦语

    DAN

    1252  (ANSI - 拉丁文 I)

    1031

    德国

    德语(德国)

    DEU

    1252  (ANSI - 拉丁文 I)

    1032

    希腊

    希腊语

    ELL

    1253  (ANSI - 希腊文)

    1033

    美国

    英语(美国)

    ENU

    1252  (ANSI - 拉丁文 I)

    1034

    西班牙

    西班牙语(传统)

    ESP

    1252  (ANSI - 拉丁文 I)

    1035

    芬兰

    芬兰语

    FIN

    1252  (ANSI - 拉丁文 I)

    1036

    法国

    法语(法国)

    FRA

    1252  (ANSI - 拉丁文 I)

    1037

    以色列

    希伯来语

    HEB

    1255  (ANSI - 希伯来文)

    1038

    匈牙利

    匈牙利语

    HUN

    1250  (ANSI - 中欧)

    1039

    冰岛

    冰岛语

    ISL

    1252  (ANSI - 拉丁文 I)

    1040

    意大利

    意大利语(意大利)

    ITA

    1252  (ANSI - 拉丁文 I)

    1041

    日本

    日语

    JPN

    932   (ANSI/OEM - 日文 Shift-JIS)

    1042

    朝鲜

    朝鲜语

    KOR

    949   (ANSI/OEM - 韩文)

    1043

    荷兰

    荷兰语(荷兰)

    NLD

    1252  (ANSI - 拉丁文 I)

    1044

    挪威

    挪威语(伯克梅尔)

    NOR

    1252  (ANSI - 拉丁文 I)

    1045

    波兰

    波兰语

    PLK

    1250  (ANSI - 中欧)

    1046

    巴西

    葡萄牙语(巴西)

    PTB

    1252  (ANSI - 拉丁文 I)

    1048

    罗马尼亚

    罗马尼亚语

    ROM

    1250  (ANSI - 中欧)

    1049

    俄罗斯

    俄语

    RUS

    1251  (ANSI - 西里尔文)

    1050

    克罗地亚

    克罗地亚语

    HRV

    1250  (ANSI - 中欧)

    1051

    斯洛伐克语

    斯洛伐克语

    SKY

    1250  (ANSI - 中欧)

    1052

    阿尔巴尼亚

    阿尔巴尼亚语

    SQI

    1250  (ANSI - 中欧)

    1053

    瑞典

    瑞典语

    SVE

    1252  (ANSI - 拉丁文 I)

    1054

    泰国

    泰语

    THA

    874   (ANSI/OEM - 泰文)

    1055

    土耳其

    土耳其语

    TRK

    1254  (ANSI - 土耳其文)

    1056

    巴基斯坦伊斯兰共和国

    乌都语

    URD

    1256  (ANSI - 阿拉伯文)

    1057

    印度尼西亚

    印度尼西亚语

    IND

    1252  (ANSI - 拉丁文 I)

    1058

    乌克兰

    乌克兰语

    UKR

    1251  (ANSI - 西里尔文)

    1059

    比利时

    比利时语

    BEL

    1251  (ANSI - 西里尔文)

    1060

    斯洛文尼亚

    斯洛文尼亚语

    SLV

    1250  (ANSI - 中欧)

    1061

    爱沙尼亚

    爱沙尼亚语

    ETI

    1257  (ANSI - 波罗的海文)

    1062

    拉脱维亚

    拉脱维亚语

    LVI

    1257  (ANSI - 波罗的海文)

    1063

    立陶宛

    立陶宛语

    LTH

    1257  (ANSI - 波罗的海文)

    1065

    伊朗

    法斯语

    FAR

    1256  (ANSI - 阿拉伯文)

    1066

    越南

    越南语

    VIT

    1258  (ANSI/OEM - 越南)

    1068

    阿塞拜疆

    阿塞拜疆语(拉丁文)

    AZE

    1254  (ANSI - 土耳其文)

    1069

    西班牙

    巴士克语

    EUQ

    1252  (ANSI - 拉丁文 I)

    1071

    前南斯拉夫马其顿共和国

    马其顿语(FYROM)

    MKI

    1251  (ANSI - 西里尔文)

    1078

    南非

    南非语

    AFK

    1252  (ANSI - 拉丁文 I)

    1080

    法罗群岛

    法罗语

    FOS

    1252  (ANSI - 拉丁文 I)

    1086

    马来西亚

    马来语(马来西亚)

    MSL

    1252  (ANSI - 拉丁文 I)

    1087

    吉尔吉斯坦

    哈萨克语

    KKZ

    1251  (ANSI - 西里尔文)

    1088

    吉尔吉斯斯坦

    吉尔吉斯语 (西里尔文)

    KYR

    1251  (ANSI - 西里尔文)

    1089

    肯尼亚

    斯瓦希里语

    SWK

    1252  (ANSI - 拉丁文 I)

    1091

    乌兹别克斯坦

    乌兹别克语(拉丁文)

    UZB

    1254  (ANSI - 土耳其文)

    1092

    鞑靼斯坦

    鞑靼语

    TTT

    1251  (ANSI - 西里尔文)

    1104

    蒙古

    蒙古语(西里尔文)

    MON

    1251  (ANSI - 西里尔文)

    1110

    西班牙

    加里西亚语

    GLC

    1252  (ANSI - 拉丁文 I)

    2049

    伊拉克

    阿拉伯语(伊拉克)

    ARI

    1256  (ANSI - 阿拉伯文)

    2052

    中华人民共和国

    中文(中国)

    CHS

    936   (ANSI/OEM - 简体中文 GBK)

    2055

    瑞士

    德语(瑞士)

    DES

    1252  (ANSI - 拉丁文 I)

    2057

    英国

    英语(英国)

    ENG

    1252  (ANSI - 拉丁文 I)

    2058

    墨西哥

    西班牙语(墨西哥)

    ESM

    1252  (ANSI - 拉丁文 I)

    2060

    比利时

    法语(比利时)

    FRB

    1252  (ANSI - 拉丁文 I)

    2064

    瑞士

    意大利语(瑞士)

    ITS

    1252  (ANSI - 拉丁文 I)

    2067

    比利时

    荷兰语(比利时)

    NLB

    1252  (ANSI - 拉丁文 I)

    2068

    挪威

    挪威语(尼诺斯克)

    NON

    1252  (ANSI - 拉丁文 I)

    2070

    葡萄牙

    葡萄牙语(葡萄牙)

    PTG

    1252  (ANSI - 拉丁文 I)

    2074

    塞尔维亚

    塞尔维亚语(拉丁文)

    SRL

    1250  (ANSI - 中欧)

    2077

    芬兰

    瑞典语(芬兰)

    SVF

    1252  (ANSI - 拉丁文 I)

    2092

    阿塞拜疆

    阿塞拜疆语(西里尔文)

    AZE

    1251  (ANSI - 西里尔文)

    2110

    文莱达鲁萨兰

    马来语(文莱达鲁萨兰)

    MSB

    1252  (ANSI - 拉丁文 I)

    2115

    乌兹别克斯坦

    乌兹别克语(西里尔文)

    UZB

    1251  (ANSI - 西里尔文)

    3073

    埃及

    阿拉伯语(埃及)

    ARE

    1256  (ANSI - 阿拉伯文)

    3076

    香港特别行政区

    中文(香港特别行政区)

    ZHH

    950   (ANSI/OEM - 繁体中文 Big5)

    3079

    奥地利

    德语(奥地利)

    DEA

    1252  (ANSI - 拉丁文 I)

    3081

    澳大利亚

    英语(澳大利亚)

    ENA

    1252  (ANSI - 拉丁文 I)

    3082

    西班牙

    西班牙语(国际)

    ESN

    1252  (ANSI - 拉丁文 I)

    3084

    加拿大

    法语(加拿大)

    FRC

    1252  (ANSI - 拉丁文 I)

    3098

    塞尔维亚

    塞尔维亚语(西里尔文)

    SRB

    1251  (ANSI - 西里尔文)

    4097

    利比亚

    阿拉伯语(利比亚)

    ARL

    1256  (ANSI - 阿拉伯文)

    4100

    新加坡

    中文(新加坡)

    ZHI

    936   (ANSI/OEM - 简体中文 GBK)

    4103

    卢森堡

    德语(卢森堡)

    DEL

    1252  (ANSI - 拉丁文 I)

    4105

    加拿大

    英语(加拿大)

    ENC

    1252  (ANSI - 拉丁文 I)

    4106

    危地马拉

    西班牙语(危地马拉)

    ESG

    1252  (ANSI - 拉丁文 I)

    4108

    瑞士

    法语(瑞士)

    FRS

    1252  (ANSI - 拉丁文 I)

    5121

    阿尔及利亚

    阿拉伯语(阿尔及利亚)

    ARG

    1256  (ANSI - 阿拉伯文)

    5124

    澳门特别行政区

    中文(澳门特别行政区)

    ZHM

    950   (ANSI/OEM - 繁体中文 Big5)

    5127

    列支敦士登

    德语(列支敦士登)

    DEC

    1252  (ANSI - 拉丁文 I)

    5129

    新西兰

    英语(新西兰)

    ENZ

    1252  (ANSI - 拉丁文 I)

    5130

    哥斯达黎加

    西班牙语(哥斯达黎加)

    ESC

    1252  (ANSI - 拉丁文 I)

    5132

    卢森堡

    法语(卢森堡)

    FRL

    1252  (ANSI - 拉丁文 I)

    6145

    摩洛哥

    阿拉伯语(摩洛哥)

    ARM

    1256  (ANSI - 阿拉伯文)

    6153

    爱尔兰

    英语(爱尔兰)

    ENI

    1252  (ANSI - 拉丁文 I)

    6154

    巴拿马

    西班牙语(巴拿马)

    ESA

    1252  (ANSI - 拉丁文 I)

    6156

    摩纳哥公国

    法语(摩纳哥)

    FRM

    1252  (ANSI - 拉丁文 I)

    7169

    突尼斯

    阿拉伯语(突尼斯)

    ART

    1256  (ANSI - 阿拉伯文)

    7177

    南非

    英语(南非)

    ENS

    1252  (ANSI - 拉丁文 I)

    7178

    多米尼加共和国

    西班牙语(多米尼加共和国)

    ESD

    1252  (ANSI - 拉丁文 I)

    8193

    阿曼

    阿拉伯语(阿曼)

    ARO

    1256  (ANSI - 阿拉伯文)

    8201

    牙买加

    英语(牙买加)

    ENJ

    1252  (ANSI - 拉丁文 I)

    8202

    委内瑞拉

    西班牙语(委内瑞拉)

    ESV

    1252  (ANSI - 拉丁文 I)

    9217

    也门

    阿拉伯语(也门)

    ARY

    1256  (ANSI - 阿拉伯文)

    9225

    加勒比海

    英语(加勒比海)

    ENB

    1252  (ANSI - 拉丁文 I)

    9226

    哥伦比亚

    西班牙语(哥伦比亚)

    ESO

    1252  (ANSI - 拉丁文 I)

    10241

    叙利亚

    阿拉伯语(叙利亚)

    ARS

    1256  (ANSI - 阿拉伯文)

    10249

    伯利兹

    英语(伯利兹)

    ENL

    1252  (ANSI - 拉丁文 I)

    10250

    秘鲁

    西班牙语(秘鲁)

    ESR

    1252  (ANSI - 拉丁文 I)

    11265

    约旦

    阿拉伯语(约旦)

    ARJ

    1256  (ANSI - 阿拉伯文)

    11273

    特立尼达和多巴哥

    英语(特立尼达)

    ENT

    1252  (ANSI - 拉丁文 I)

    11274

    阿根廷

    西班牙语(阿根廷)

    ESS

    1252  (ANSI - 拉丁文 I)

    12289

    黎巴嫩

    阿拉伯语(黎巴嫩)

    ARB

    1256  (ANSI - 阿拉伯文)

    12297

    津巴布韦

    英语(津巴布韦)

    ENW

    1252  (ANSI - 拉丁文 I)

    12298

    厄瓜多尔

    西班牙语(厄瓜多尔)

    ESF

    1252  (ANSI - 拉丁文 I)

    13313

    科威特

    阿拉伯语(科威特)

    ARK

    1256  (ANSI - 阿拉伯文)

    13321

    菲律宾共和国

    英语(菲律宾)

    ENP

    1252  (ANSI - 拉丁文 I)

    13322

    智利

    西班牙语(智利)

    ESL

    1252  (ANSI - 拉丁文 I)

    14337

    阿联酋

    阿拉伯语(阿联酋)

    ARU

    1256  (ANSI - 阿拉伯文)

    14346

    乌拉圭

    西班牙语(乌拉圭)

    ESY

    1252  (ANSI - 拉丁文 I)

    15361

    巴林

    阿拉伯语(巴林)

    ARH

    1256  (ANSI - 阿拉伯文)

    15370

    巴拉圭

    西班牙语(巴拉圭)

    ESZ

    1252  (ANSI - 拉丁文 I)

    16385

    卡塔尔

    阿拉伯语(卡塔尔)

    ARQ

    1256  (ANSI - 阿拉伯文)

    16394

    玻利维亚

    西班牙语(玻利维亚)

    ESB

    1252  (ANSI - 拉丁文 I)

    17418

    萨尔瓦多

    西班牙语(萨尔瓦多)

    ESE

    1252  (ANSI - 拉丁文 I)

    18442

    洪都拉斯

    西班牙语(洪都拉斯)

    ESH

    1252  (ANSI - 拉丁文 I)

    19466

    尼加拉瓜

    西班牙语(尼加拉瓜)

    ESI

    1252  (ANSI - 拉丁文 I)

    20490

    波多黎各(美)

    西班牙语(波多黎各(美))

    ESU

    1252  (ANSI - 拉丁文 I)

    LCID取决于语言,在表中列出国家名只是为了增加趣味性。例如可以看到以色列还在使用古老的希伯来语。“希伯来语”的法文是hébreu,这个单词还有一个意思,就是“不能理解的东西”。

    展开全文
  • 关于字符编码,你所需要知道的

    千次阅读 2013-11-27 15:01:53
    字符编码的问题看似很小,经常被技术人员忽视,但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识,希望对大家有所帮助。 还是得从ASCII码说起   说到字符编码,不得不说ASCII码的...

    字符编码的问题看似很小,经常被技术人员忽视,但是很容易导致一些莫名其妙的问题。这里总结了一下字符编码的一些普及性的知识,希望对大家有所帮助。

    还是得从ASCII码说起

     

    说到字符编码,不得不说ASCII码的简史。计算机一开始发明的时候是用来解决数字计算的问题,后来人们发现,计算机还可以做更多的事,例如文本处理。但由于计算机只识“数”,因此人们必须告诉计算机哪个数字来代表哪个特定字符,例如65代表字母‘A’,66代表字母‘B’,以此类推。但是计算机之间字符-数字的对应关系必须得一致,否则就会造成同一段数字在不同计算机上显示出来的字符不一样。因此美国国家标准协会ANSI制定了一个标准,规定了常用字符的集合以及每个字符对应的编号,这就是ASCII字符集(Character Set),也称ASCII码。

    当时的计算机普遍使用8比特字节作为最小的存储和处理单元,加之当时用到的字符也很少,26个大小写英文字母还有数字再加上其他常用符号,也不到100个,因此使用7个比特位就可以高效的存储和处理ASCII码,剩下最高位1比特被用作一些通讯系统的奇偶校验。

    注意,字节代表系统能够处理的最小单位,不一定是8比特。只是现代计算机的事实标准就是用8比特来代表一个字节。在很多技术规格文献中,为了避免产生歧义,更倾向于使用8位组(Octet)而不是字节(Byte)这个术语来强调8个比特的二进制流。下文中为了便于理解,我会延用大家熟悉的“字节”这个概念。

    ASCII table

    ASCII字符集由95个可打印字符(0x20-0x7E)和33个控制字符(0x00-0x19,0x7F)组成。可打印字符用于显示在输出设备上,例如荧屏或者打印纸上,控制字符用于向计算机发出一些特殊指令,例如0x07会让计算机发出哔的一声,0x00通常用于指示字符串的结束,0x0D和0x0A用于指示打印机的打印针头退到行首(回车)并移到下一行(换行)。

    那时候的字符编解码系统非常简单,就是简单的查表过程。例如将字符序列编码为二进制流写入存储设备,只需要在ASCII字符集中依次找到字符对应的字节,然后直接将该字节写入存储设备即可。解码二进制流的过程也是类似。

    OEM字符集的衍生

    当计算机开始发展起来的时候,人们逐渐发现,ASCII字符集里那可怜的128个字符已经不能再满足他们的需求了。人们就在想,一个字节能够表示的数字(编号)有256个,而ASCII字符只用到了0x00~0x7F,也就是占用了前128个,后面128个数字不用白不用,因此很多人打起了后面这128个数字的主意。可是问题在于,很多人同时有这样的想法,但是大家对于0x80-0xFF这后面的128个数字分别对应什么样的字符,却有各自的想法。这就导致了当时销往世界各地的机器上出现了大量各式各样的OEM字符集。

    下面这张表是IBM-PC机推出的其中一个OEM字符集,字符集的前128个字符和ASCII字符集的基本一致(为什么说基本一致呢,是因为前32个控制字符在某些情况下会被IBM-PC机当作可打印字符解释),后面128个字符空间加入了一些欧洲国家用到的重音字符,以及一些用于画线条画的字符。

    IBM-PC OEM字符集

    事实上,大部分OEM字符集是兼容ASCII字符集的,也就是说,大家对于0x00~0x7F这个范围的解释基本是相同的,而对于后半部分0x80~0xFF的解释却不一定相同。甚至有时候同样的字符在不同OEM字符集中对应的字节也是不同的。

    不同的OEM字符集导致人们无法跨机器交流各种文档。例如职员甲发了一封简历résumés给职员乙,结果职员乙看到的却是rגsumגs,因为é字符在职员甲机器上的OEM字符集中对应的字节是0x82,而在职员乙的机器上,由于使用的OEM字符集不同,对0x82字节解码后得到的字符却是ג

    多字节字符集(MBCS)和中文字符集

    上面我们提到的字符集都是基于单字节编码,也就是说,一个字节翻译成一个字符。这对于拉丁语系国家来说可能没有什么问题,因为他们通过扩展第8个比特,就可以得到256个字符了,足够用了。但是对于亚洲国家来说,256个字符是远远不够用的。因此这些国家的人为了用上电脑,又要保持和ASCII字符集的兼容,就发明了多字节编码方式,相应的字符集就称为多字节字符集。例如中国使用的就是双字节字符集编码(DBCS,Double Byte Character Set)。

    对于单字节字符集来说,代码页中只需要有一张码表即可,上面记录着256个数字代表的字符。程序只需要做简单的查表操作就可以完成编解码的过程。

    代码页是字符集编码的具体实现,你可以把他理解为一张“字符-字节”映射表,通过查表实现“字符-字节”的翻译。下面会有更详细的描述。

    而对于多字节字符集,代码页中通常会有很多码表。那么程序怎么知道该使用哪张码表去解码二进制流呢?答案是,根据第一个字节来选择不同的码表进行解析

    例如目前最常用的中文字符集GB2312,涵盖了所有简体字符以及一部分其他字符;GBK(K代表扩展的意思)则在GB2312的基础上加入了对繁体字符等其他非简体字符(GB18030字符集不是双字节字符集,我们在讲Unicode的时候会提到)。这两个字符集的字符都是使用1-2个字节来表示。Windows系统采用936代码页来实现对GBK字符集的编解码。在解析字节流的时候,如果遇到字节的最高位是0的话,那么就使用936代码页中的第1张码表进行解码,这就和单字节字符集的编解码方式一致了。

    image

    当字节的高位是1的时候,确切的说,当第一个字节位于0x81–0xFE之间时,根据第一个字节不同找到代码页中的相应的码表,例如当第一个字节是0x81,那么对应936中的下面这张码表:

    image

    (关于936代码页中完整的码表信息,参见MSDN:http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx.)

    按照936代码页的码表,当程序遇到连续字节流0x81 0x40的时候,就会解码为“丂”字符。

    ANSI标准、国家标准、ISO标准

    不同ASCII衍生字符集的出现,让文档交流变得非常困难,因此各种组织都陆续进行了标准化流程。例如美国ANSI组织制定了ANSI标准字符编码(注意,我们现在通常说到ANSI编码,通常指的是平台的默认编码,例如英文操作系统中是ISO-8859-1,中文系统是GBK),ISO组织制定的各种ISO标准字符编码,还有各国也会制定一些国家标准字符集,例如中国的GBK,GB2312和GB18030。

    操作系统在发布的时候,通常会往机器里预装这些标准的字符集还有平台专用的字符集,这样只要你的文档是使用标准字符集编写的,通用性就比较高了。例如你用GB2312字符集编写的文档,在中国大陆内的任何机器上都能正确显示。同时,我们也可以在一台机器上阅读多个国家不同语言的文档了,前提是本机必须安装该文档使用的字符集。

    Unicode的出现

    虽然通过使用不同字符集,我们可以在一台机器上查阅不同语言的文档,但是我们仍然无法解决一个问题:在一份文档中显示所有字符。为了解决这个问题,我们需要一个全人类达成共识的巨大的字符集,这就是Unicode字符集。

    Unicode字符集概述

    Unicode字符集涵盖了目前人类使用的所有字符,并为每个字符进行统一编号,分配唯一的字符码(Code Point)。Unicode字符集将所有字符按照使用上的频繁度划分为17个层面(Plane),每个层面上有216=65536个字符码空间。

    image

    其中第0个层面BMP,基本涵盖了当今世界用到的所有字符。其他的层面要么是用来表示一些远古时期的文字,要么是留作扩展。我们平常用到的Unicode字符,一般都是位于BMP层面上的。目前Unicode字符集中尚有大量字符空间未使用。

    编码系统的变化

    在Unicode出现之前,所有的字符集都是和具体编码方案绑定在一起的,都是直接将字符和最终字节流绑定死了,例如ASCII编码系统规定使用7比特来编码ASCII字符集;GB2312以及GBK字符集,限定了使用最多2个字节来编码所有字符,并且规定了字节序。这样的编码系统通常用简单的查表,也就是通过代码页就可以直接将字符映射为存储设备上的字节流了。例如下面这个例子:

    image

    这种方式的缺点在于,字符和字节流之间耦合得太紧密了,从而限定了字符集的扩展能力。假设以后火星人入住地球了,要往现有字符集中加入火星文就变得很难甚至不可能了,而且很容易破坏现有的编码规则。

    因此Unicode在设计上考虑到了这一点,将字符集和字符编码方案分离开。

    字符编码系统

    也就是说,虽然每个字符在Unicode字符集中都能找到唯一确定的编号(字符码,又称Unicode码),但是决定最终字节流的却是具体的字符编码。例如同样是对Unicode字符“A”进行编码,UTF-8字符编码得到的字节流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。

    常见的Unicode编码

    UCS-2/UTF-16

    如果要我们来实现Unicode字符集中BMP字符的编码方案,我们会怎么实现?由于BMP层面上有216=65536个字符码,因此我们只需要两个字节就可以完全表示这所有的字符了。

    举个例子,“中”的Unicode字符码是0x4E2D(01001110 00101101),那么我们可以编码为01001110 00101101(大端)或者00101101 01001110 (小端)。

    UCS-2和UTF-16对于BMP层面的字符均是使用2个字节来表示,并且编码得到的结果完全一致。不同之处在于,UCS-2最初设计的时候只考虑到BMP字符,因此使用固定2个字节长度,也就是说,他无法表示Unicode其他层面上的字符,而UTF-16为了解除这个限制,支持Unicode全字符集的编解码,采用了变长编码,最少使用2个字节,如果要编码BMP以外的字符,则需要4个字节结对,这里就不讨论那么远,有兴趣可以参考维基百科:UTF-16/UCS-2

    Windows从NT时代开始就采用了UTF-16编码,很多流行的编程平台,例如.Net,Java,Qt还有Mac下的Cocoa等都是使用UTF-16作为基础的字符编码。例如代码中的字符串,在内存中相应的字节流就是用UTF-16编码过的。

    UTF-8

    UTF-8应该是目前应用最广泛的一种Unicode编码方案。由于UCS-2/UTF-16对于ASCII字符使用两个字节进行编码,存储和处理效率相对低下,并且由于ASCII字符经过UTF-16编码后得到的两个字节,高字节始终是0x00,很多C语言的函数都将此字节视为字符串末尾从而导致无法正确解析文本。因此一开始推出的时候遭到很多西方国家的抵触,大大影响了Unicode的推行。后来聪明的人们发明了UTF-8编码,解决了这个问题。

    UTF-8编码方案采用1-4个字节来编码字符,方法其实也非常简单。

    image

    (上图中的x代表Unicode码的低8位,y代表高8位)

    对于ASCII字符的编码使用单字节,和ASCII编码一摸一样,这样所有原先使用ASCII编解码的文档就可以直接转到UTF-8编码了。对于其他字符,则使用2-4个字节来表示,其中,首字节前置1的数目代表正确解析所需要的字节数,剩余字节的高2位始终是10。例如首字节是1110yyyy,前置有3个1,说明正确解析总共需要3个字节,需要和后面2个以10开头的字节结合才能正确解析得到字符

    关于UTF-8的更多信息,参考维基百科:UTF-8

    GB18030

    任何能够将Unicode字符映射为字节流的编码都属于Unicode编码。中国的GB18030编码,覆盖了Unicode所有的字符,因此也算是一种Unicode编码。只不过他的编码方式并不像UTF-8或者UTF-16一样,将Unicode字符的编号通过一定的规则进行转换,而只能通过查表的手段进行编码。

    关于GB18030的更多信息,参考:GB18030

    Unicode相关的常见问题

    Unicode是两个字节吗?

    Unicode只是定义了一个庞大的、全球通用的字符集,并为每个字符规定了唯一确定的编号,具体存储为什么样的字节流,取决于字符编码方案。推荐的Unicode编码是UTF-16和UTF-8。

    带签名的UTF-8指的是什么意思?

    带签名指的是字节流以BOM标记开始。很多软件会“智能”的探测当前字节流使用的字符编码,这种探测过程出于效率考虑,通常会提取字节流前面若干个字节,看看是否符合某些常见字符编码的编码规则。由于UTF-8和ASCII编码对于纯英文的编码是一样的,无法区分开来,因此通过在字节流最前面添加BOM标记可以告诉软件,当前使用的是Unicode编码,判别成功率就十分准确了。但是需要注意,不是所有软件或者程序都能正确处理BOM标记,例如PHP就不会检测BOM标记,直接把它当普通字节流解析了。因此如果你的PHP文件是采用带BOM标记的UTF-8进行编码的,那么有可能会出现问题。

    Unicode编码和以前的字符集编码有什么区别?

    早期字符编码、字符集和代码页等概念都是表达同一个意思。例如GB2312字符集、GB2312编码,936代码页,实际上说的是同个东西。但是对于Unicode则不同,Unicode字符集只是定义了字符的集合和唯一编号,Unicode编码,则是对UTF-8、UCS-2/UTF-16等具体编码方案的统称而已,并不是具体的编码方案。所以当需要用到字符编码的时候,你可以写gb2312,codepage936,utf-8,utf-16,但请不要写unicode(看过别人在网页的meta标签里头写charset=unicode,有感而发)。

     

    乱码问题

    乱码指的是程序显示出来的字符文本无法用任何语言去解读。一般情况下会包含大量?或者�。乱码问题是所有计算机用户或多或少会遇到的问题。造成乱码的原因就是因为使用了错误的字符编码去解码字节流因此当我们在思考任何跟文本显示有关的问题时,请时刻保持清醒:当前使用的字符编码是什么。只有这样,我们才能正确分析和处理乱码问题。

    例如最常见的网页乱码问题。如果你是网站技术人员,遇到这样的问题,需要检查以下原因:

    • 服务器返回的响应头Content-Type没有指明字符编码
    • 网页内是否使用META HTTP-EQUIV标签指定了字符编码
    • 网页文件本身存储时使用的字符编码和网页声明的字符编码是否一致  

    image image

    注意,网页解析的过程如果使用的字符编码不正确,还可能会导致脚本或者样式表出错。具体细节可以参考我以前写过的文章:文档字符集导致的脚本错误Asp.Net页面的编码问题

    不久前看到某技术论坛有人反馈,WinForm程序使用Clipboard类的GetData方法去访问剪切板中的HTML内容时会出现乱码的问题,我估计也是由于WinForm在获取HTML文本的时候没有用对正确的字符编码导致的。Windows剪贴板只支持UTF-8编码,也就是说你传入的文本都会被UTF-8编解码。这样一来,只要两个程序都是调用Windows剪切板API编程的话,那么复制粘贴的过程中不会出现乱码。除非一方在获取到剪贴板数据之后使用了错误的字符编码进行解码,才会得到乱码(我做了简单的WinForm剪切板编程实验,发现GetData使用的是系统默认编码,而不是UTF-8编码)。

    关于乱码中出现?或者�,这里需要额外提一下,当程序使用特定字符编码解析字节流的时候,一旦遇到无法解析的字节流时,就会用?或者�来替代。因此,一旦你最终解析得到的文本包含这样的字符,而你又无法得到原始字节流的时候,说明正确的信息已经彻底丢失了,尝试任何字符编码都无法从这样的字符文本中还原出正确的信息来

    必要的术语解释

    字符集(Character Set),字面上的理解就是字符的集合,例如ASCII字符集,定义了128个字符;GB2312定义了7445个字符。而计算机系统中提到的字符集准确来说,指的是已编号的字符的有序集合(不一定是连续)

    字符码(Code Point)指的就是字符集中每个字符的数字编号。例如ASCII字符集用0-127这连续的128个数字分别表示128个字符;GBK字符集使用区位码的方式为每个字符编号,首先定义一个94X94的矩阵,行称为“区”,列称为“位”,然后将所有国标汉字放入矩阵当中,这样每个汉字就可以用唯一的“区位”码来标识了。例如“中”字被放到54区第48位,因此字符码就是5448。而Unicode中将字符集按照一定的类别划分到0~16这17个层面(Planes)中,每个层面中拥有216=65536个字符码,因此Unicode总共拥有的字符码,也即是Unicode的字符空间总共有17*65536=1114112。

    image

    编码的过程是将字符转换成字节流。

    解码的过程是将字节流解析为字符。

    字符编码(Character Encoding)是将字符集中的字符码映射为字节流的一种具体实现方案。例如ASCII字符编码规定使用单字节中低位的7个比特去编码所有的字符。例如‘A’的编号是65,用单字节表示就是0x41,因此写入存储设备的时候就是b’01000001’。GBK编码则是将区位码(GBK的字符码)中的区码和位码的分别加上0xA0(160)的偏移(之所以要加上这样的偏移,主要是为了和ASCII码兼容),例如刚刚提到的“中”字,区位码是5448,十六进制是0x3630,区码和位码分别加上0xA0的偏移之后就得到0xD6D0,这就是“中”字的GBK编码结果。

    代码页(Code Page)一种字符编码具体形式。早期字符相对少,因此通常会使用类似表格的形式将字符直接映射为字节流,然后通过查表的方式来实现字符的编解码。现代操作系统沿用了这种方式。例如Windows使用936代码页、Mac系统使用EUC-CN代码页实现GBK字符集的编码,名字虽然不一样,但对于同一汉字的编码肯定是一样的。

    大小端的说法源自《格列佛游记》。我们知道,鸡蛋通常一端大一端小,小人国的人们对于剥蛋壳时应从哪一端开始剥起有着不一样的看法。同样,计算机界对于传输多字节字(由多个字节来共同表示一个数据类型)时,是先传高位字节(大端)还是先传低位字节(小端)也有着不一样的看法,这就是计算机里头大小端模式的由来了。无论是写文件还是网络传输,实际上都是往流设备进行写操作的过程,而且这个写操作是从流的低地址向高地址开始写(这很符合人的习惯),对于多字节字来说,如果先写入高位字节,则称作大端模式。反之则称作小端模式。也就是说,大端模式下,字节序和流设备的地址顺序是相反的,而小端模式则是相同的。一般网络协议都采用大端模式进行传输。

    展开全文
  • Windows程序中的字符编码

    千次阅读 2006-09-14 14:11:00
    我们在使用和安装Windows程序时,有时会看到以...我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间

    我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹(如Office),这些数字似乎和字符集有关,但它们究竟是什么意思呢?研究这个问题的同时,又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间或乐趣点缀的旅程。

    0 Where is Win32 API

    Windows程序有用户态和核心态的说法。在32位地址空间中,0×80000000以下属于用户态(0×0~0×7FFFFFFF=2GB,0×0~0×10000是保留的),0×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(高级Win32应用程序接口)。这些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 Linux”

    bootsect.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负责用户登录,在完成登录后,它会启动注册表HKLM/SOFTWARE/Microsoft/Windows NT/Current Version/Winlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置,让硬盘响个不停,并考验我们的耐性,最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后,我们就会看到熟悉的开始菜单和桌面。

    1 Win32 API的A/W函数

    要了解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 ptr [esp+04]
    int 2E
    ret 002C
    可见这个native API只是简单地调用了核心态提供的0×20号system service。

    例3:gdi32.dll中的GetGlyphOutline函数可以获取指定字符的字模。GetGlyphOutlineA和GetGlyphOutlineW函数都会调用同一个内部函数(记作F)。函数F在返回前将通过int 2E调用0×10B1号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 0×04
    #define LANG_ENGLISH 0×09
    #define LANG_FRENCH 0×0c
    #define LANG_GERMAN 0×07

    #define SUBLANG_CHINESE_TRADITIONAL 0×01 // Chinese (Taiwan Region)
    #define SUBLANG_CHINESE_SIMPLIFIED 0×02 // Chinese (PR China)
    #define SUBLANG_ENGLISH_US 0×01 // English (USA)
    #define SUBLANG_ENGLISH_UK 0×02 // 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代码页都是“936 (ANSI/OEM - Simplified Chinese GBK)”。  Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM - Traditional Chinese Big5)”。

    附录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

    C RunTime库(CRT)使用_UNICODE和_MBCS来区分三套字符串处理函数,分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen ,猜猜以下函数返回几?

    strlen(”VOIP网关”);
    _mbslen((unsigned char *)”VOIP网关”);
    wcslen(L”VOIP网关”);

    答案是8、6、6。L”ANSI字符串”通知编译器将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里的菜单。

    在我的个人主页(http://fmddlmyy.home4u.china.com)上可以下载修改过的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是Chinese (PRC),用户的Locale是English (US),程序中硬编码了字符串“文件”。 Chinese (PRC)的ANSI代码页是GBK,“文件”的编码“CE C4 BC FE”。English (US)的ANSI代码页是Latin I,用户按照Latin I编码去解释“CE C4 BC FE”,就会看到“Îļþ”。

    回答我前面提过的一个问题:Delphi程序根据用户LCID转换资源中的字符串。如果用户LCID是Chinese (PRC),系统LCID是English (US)。那么资源中的Unicode字符串会被转换为GBK编码,然后按照Latin I显示,这时我们看到的就是类似“Îļþ”的东东,不是??。

    既然资源是以Unicode保存的,MBCS程序如果不将其转换到ANSI代码页,而用W版本的函数直接显示,就不会产生乱码。例如MFC程序菜单里的中文,在English (US)的Locale也可以正常显示。不过这取决于各部分代码的具体实现,menu bar控件里的中文在English (US)的Locale会全部显示成??。

    进一步的参考资料

    本文的第0节和附录0主要参考了《Inside Windows 2000 Third Edition》,国内出过该书的影印版。DDK文档中有大量Windows内核的信息。用Win32dsm和各种调试器查看Windows系统文件可以获得更直接的信息。

    关于Window程序的字符编码,最好的参考资料是winnt.h等SDK的包含文件、VCL、MFC、CRT的源文件。我们不需要阅读它们,只要找到自己感兴趣的信息就可以了,用Source Insight可能方便一些。

    本文所谈的不是什么万古不迁的道理,只是别的程序员的一些设定,我们因为需要使用他们的程序,所以有必要了解一些细节。研究问题的方法和兴趣永远比问题本身重要,如一句拉丁俗语所说:res, non verba,实质胜于文字。

    尾声

    “明月虽有圆缺,但毕竟永恒不灭,人生却如过眼烟云,一去不回,真不知计较为何?”

    “蛙声虽是短促,但却是万籁中一个活泼的禅机,也可以说万古如斯,永恒不迁,无奈感受到的,能有几人?”

    这是一本武侠书中的对话。在时间的长河中,人生和蛙声一样易逝。说到蛙声,我的20个月的小宝宝在喝汤后,略加酝酿,就会紧闭着嘴巴,发出很像蛙鸣的声音。我们会逗他说:“小青蛙又来了”。小家伙益发得意,不管我的抗议,将连汤带油的小下巴亲热地贴在我的身上。

     

    附录1 一些关于LCID的信息

    使用EnumSystemLocales函数可以枚举系统支持的LCID。用GetLocaleInfo可以得到ANSI代码页的ID,再通过GetCPInfoEx可以获得代码页的全称。以下是我在中文Windows XP上读到的内容。

    LCID国家或地区语言语言缩写ANSI代码页
    1025沙特阿拉伯阿拉伯语(沙特阿拉伯)ARA1256  (ANSI - 阿拉伯文)
    1026保加利亚保加利亚语BGR1251  (ANSI - 西里尔文)
    1027西班牙加泰隆语CAT1252  (ANSI - 拉丁文 I)
    1028台湾中文(台湾)CHT950   (ANSI/OEM - 繁体中文 Big5)
    1029捷克共和国捷克语CSY1250  (ANSI - 中欧)
    1030丹麦丹麦语DAN1252  (ANSI - 拉丁文 I)
    1031德国德语(德国)DEU1252  (ANSI - 拉丁文 I)
    1032希腊希腊语ELL1253  (ANSI - 希腊文)
    1033美国英语(美国)ENU1252  (ANSI - 拉丁文 I)
    1034西班牙西班牙语(传统)ESP1252  (ANSI - 拉丁文 I)
    1035芬兰芬兰语FIN1252  (ANSI - 拉丁文 I)
    1036法国法语(法国)FRA1252  (ANSI - 拉丁文 I)
    1037以色列希伯来语HEB1255  (ANSI - 希伯来文)
    1038匈牙利匈牙利语HUN1250  (ANSI - 中欧)
    1039冰岛冰岛语ISL1252  (ANSI - 拉丁文 I)
    1040意大利意大利语(意大利)ITA1252  (ANSI - 拉丁文 I)
    1041日本日语JPN932   (ANSI/OEM - 日文 Shift-JIS)
    1042朝鲜朝鲜语KOR949   (ANSI/OEM - 韩文)
    1043荷兰荷兰语(荷兰)NLD1252  (ANSI - 拉丁文 I)
    1044挪威挪威语(伯克梅尔)NOR1252  (ANSI - 拉丁文 I)
    1045波兰波兰语PLK1250  (ANSI - 中欧)
    1046巴西葡萄牙语(巴西)PTB1252  (ANSI - 拉丁文 I)
    1048罗马尼亚罗马尼亚语ROM1250  (ANSI - 中欧)
    1049俄罗斯俄语RUS1251  (ANSI - 西里尔文)
    1050克罗地亚克罗地亚语HRV1250  (ANSI - 中欧)
    1051斯洛伐克语斯洛伐克语SKY1250  (ANSI - 中欧)
    1052阿尔巴尼亚阿尔巴尼亚语SQI1250  (ANSI - 中欧)
    1053瑞典瑞典语SVE1252  (ANSI - 拉丁文 I)
    1054泰国泰语THA874   (ANSI/OEM - 泰文)
    1055土耳其土耳其语TRK1254  (ANSI - 土耳其文)
    1056巴基斯坦伊斯兰共和国乌都语URD1256  (ANSI - 阿拉伯文)
    1057印度尼西亚印度尼西亚语IND1252  (ANSI - 拉丁文 I)
    1058乌克兰乌克兰语UKR1251  (ANSI - 西里尔文)
    1059比利时比利时语BEL1251  (ANSI - 西里尔文)
    1060斯洛文尼亚斯洛文尼亚语SLV1250  (ANSI - 中欧)
    1061爱沙尼亚爱沙尼亚语ETI1257  (ANSI - 波罗的海文)
    1062拉脱维亚拉脱维亚语LVI1257  (ANSI - 波罗的海文)
    1063立陶宛立陶宛语LTH1257  (ANSI - 波罗的海文)
    1065伊朗法斯语FAR1256  (ANSI - 阿拉伯文)
    1066越南越南语VIT1258  (ANSI/OEM - 越南)
    1067亚美尼亚亚美尼亚语HYE936   (ANSI/OEM - 简体中文 GBK)
    1068阿塞拜疆阿塞拜疆语(拉丁文)AZE1254  (ANSI - 土耳其文)
    1069西班牙巴士克语EUQ1252  (ANSI - 拉丁文 I)
    1071前南斯拉夫马其顿共和国马其顿语(FYROM)MKI1251  (ANSI - 西里尔文)
    1078南非南非语AFK1252  (ANSI - 拉丁文 I)
    1079格鲁吉亚格鲁吉亚语KAT936   (ANSI/OEM - 简体中文 GBK)
    1080法罗群岛法罗语FOS1252  (ANSI - 拉丁文 I)
    1081印度印地语HIN936   (ANSI/OEM - 简体中文 GBK)
    1086马来西亚马来语(马来西亚)MSL1252  (ANSI - 拉丁文 I)
    1087吉尔吉斯坦哈萨克语KKZ1251  (ANSI - 西里尔文)
    1088吉尔吉斯斯坦吉尔吉斯语 (西里尔文)KYR1251  (ANSI - 西里尔文)
    1089肯尼亚斯瓦希里语SWK1252  (ANSI - 拉丁文 I)
    1091乌兹别克斯坦乌兹别克语(拉丁文)UZB1254  (ANSI - 土耳其文)
    1092鞑靼斯坦鞑靼语TTT1251  (ANSI - 西里尔文)
    1094印度旁遮普语PAN936   (ANSI/OEM - 简体中文 GBK)
    1095印度古吉拉特语GUJ936   (ANSI/OEM - 简体中文 GBK)
    1097印度泰米尔语TAM936   (ANSI/OEM - 简体中文 GBK)
    1098印度泰卢固语TEL936   (ANSI/OEM - 简体中文 GBK)
    1099印度卡纳拉语KAN936   (ANSI/OEM - 简体中文 GBK)
    1102印度马拉地语MAR936   (ANSI/OEM - 简体中文 GBK)
    1103印度梵文SAN936   (ANSI/OEM - 简体中文 GBK)
    1104蒙古蒙古语(西里尔文)MON1251  (ANSI - 西里尔文)
    1110西班牙加里西亚语GLC1252  (ANSI - 拉丁文 I)
    1111印度孔卡尼语KNK936   (ANSI/OEM - 简体中文 GBK)
    1114叙利亚叙利亚语SYR936   (ANSI/OEM - 简体中文 GBK)
    1125马尔代夫第维埃语DIV936   (ANSI/OEM - 简体中文 GBK)
    2049伊拉克阿拉伯语(伊拉克)ARI1256  (ANSI - 阿拉伯文)
    2052中华人民共和国中文(中国)CHS936   (ANSI/OEM - 简体中文 GBK)
    2055瑞士德语(瑞士)DES1252  (ANSI - 拉丁文 I)
    2057英国英语(英国)ENG1252  (ANSI - 拉丁文 I)
    2058墨西哥西班牙语(墨西哥)ESM1252  (ANSI - 拉丁文 I)
    2060比利时法语(比利时)FRB1252  (ANSI - 拉丁文 I)
    2064瑞士意大利语(瑞士)ITS1252  (ANSI - 拉丁文 I)
    2067比利时荷兰语(比利时)NLB1252  (ANSI - 拉丁文 I)
    2068挪威挪威语(尼诺斯克)NON1252  (ANSI - 拉丁文 I)
    2070葡萄牙葡萄牙语(葡萄牙)PTG1252  (ANSI - 拉丁文 I)
    2074塞尔维亚塞尔维亚语(拉丁文)SRL1250  (ANSI - 中欧)
    2077芬兰瑞典语(芬兰)SVF1252  (ANSI - 拉丁文 I)
    2092阿塞拜疆阿塞拜疆语(西里尔文)AZE1251  (ANSI - 西里尔文)
    2110文莱达鲁萨兰马来语(文莱达鲁萨兰)MSB1252  (ANSI - 拉丁文 I)
    2115乌兹别克斯坦乌兹别克语(西里尔文)UZB1251  (ANSI - 西里尔文)
    3073埃及阿拉伯语(埃及)ARE1256  (ANSI - 阿拉伯文)
    3076香港特别行政区中文(香港特别行政区)ZHH950   (ANSI/OEM - 繁体中文 Big5)
    3079奥地利德语(奥地利)DEA1252  (ANSI - 拉丁文 I)
    3081澳大利亚英语(澳大利亚)ENA1252  (ANSI - 拉丁文 I)
    3082西班牙西班牙语(国际)ESN1252  (ANSI - 拉丁文 I)
    3084加拿大法语(加拿大)FRC1252  (ANSI - 拉丁文 I)
    3098塞尔维亚塞尔维亚语(西里尔文)SRB1251  (ANSI - 西里尔文)
    4097利比亚阿拉伯语(利比亚)ARL1256  (ANSI - 阿拉伯文)
    4100新加坡中文(新加坡)ZHI936   (ANSI/OEM - 简体中文 GBK)
    4103卢森堡德语(卢森堡)DEL1252  (ANSI - 拉丁文 I)
    4105加拿大英语(加拿大)ENC1252  (ANSI - 拉丁文 I)
    4106危地马拉西班牙语(危地马拉)ESG1252  (ANSI - 拉丁文 I)
    4108瑞士法语(瑞士)FRS1252  (ANSI - 拉丁文 I)
    5121阿尔及利亚阿拉伯语(阿尔及利亚)ARG1256  (ANSI - 阿拉伯文)
    5124澳门特别行政区中文(澳门特别行政区)ZHM950   (ANSI/OEM - 繁体中文 Big5)
    5127列支敦士登德语(列支敦士登)DEC1252  (ANSI - 拉丁文 I)
    5129新西兰英语(新西兰)ENZ1252  (ANSI - 拉丁文 I)
    5130哥斯达黎加西班牙语(哥斯达黎加)ESC1252  (ANSI - 拉丁文 I)
    5132卢森堡法语(卢森堡)FRL1252  (ANSI - 拉丁文 I)
    6145摩洛哥阿拉伯语(摩洛哥)ARM1256  (ANSI - 阿拉伯文)
    6153爱尔兰英语(爱尔兰)ENI1252  (ANSI - 拉丁文 I)
    6154巴拿马西班牙语(巴拿马)ESA1252  (ANSI - 拉丁文 I)
    6156摩纳哥公国法语(摩纳哥)FRM1252  (ANSI - 拉丁文 I)
    7169突尼斯阿拉伯语(突尼斯)ART1256  (ANSI - 阿拉伯文)
    7177南非英语(南非)ENS1252  (ANSI - 拉丁文 I)
    7178多米尼加共和国西班牙语(多米尼加共和国)ESD1252  (ANSI - 拉丁文 I)
    8193阿曼阿拉伯语(阿曼)ARO1256  (ANSI - 阿拉伯文)
    8201牙买加英语(牙买加)ENJ1252  (ANSI - 拉丁文 I)
    8202委内瑞拉西班牙语(委内瑞拉)ESV1252  (ANSI - 拉丁文 I)
    9217也门阿拉伯语(也门)ARY1256  (ANSI - 阿拉伯文)
    9225加勒比海英语(加勒比海)ENB1252  (ANSI - 拉丁文 I)
    9226哥伦比亚西班牙语(哥伦比亚)ESO1252  (ANSI - 拉丁文 I)
    10241叙利亚阿拉伯语(叙利亚)ARS1256  (ANSI - 阿拉伯文)
    10249伯利兹英语(伯利兹)ENL1252  (ANSI - 拉丁文 I)
    10250秘鲁西班牙语(秘鲁)ESR1252  (ANSI - 拉丁文 I)
    11265约旦阿拉伯语(约旦)ARJ1256  (ANSI - 阿拉伯文)
    11273特立尼达和多巴哥英语(特立尼达)ENT1252  (ANSI - 拉丁文 I)
    11274阿根廷西班牙语(阿根廷)ESS1252  (ANSI - 拉丁文 I)
    12289黎巴嫩阿拉伯语(黎巴嫩)ARB1256  (ANSI - 阿拉伯文)
    12297津巴布韦英语(津巴布韦)ENW1252  (ANSI - 拉丁文 I)
    12298厄瓜多尔西班牙语(厄瓜多尔)ESF1252  (ANSI - 拉丁文 I)
    13313科威特阿拉伯语(科威特)ARK1256  (ANSI - 阿拉伯文)
    13321菲律宾共和国英语(菲律宾)ENP1252  (ANSI - 拉丁文 I)
    13322智利西班牙语(智利)ESL1252  (ANSI - 拉丁文 I)
    14337阿联酋阿拉伯语(阿联酋)ARU1256  (ANSI - 阿拉伯文)
    14346乌拉圭西班牙语(乌拉圭)ESY1252  (ANSI - 拉丁文 I)
    15361巴林阿拉伯语(巴林)ARH1256  (ANSI - 阿拉伯文)
    15370巴拉圭西班牙语(巴拉圭)ESZ1252  (ANSI - 拉丁文 I)
    16385卡塔尔阿拉伯语(卡塔尔)ARQ1256  (ANSI - 阿拉伯文)
    16394玻利维亚西班牙语(玻利维亚)ESB1252  (ANSI - 拉丁文 I)
    17418萨尔瓦多西班牙语(萨尔瓦多)ESE1252  (ANSI - 拉丁文 I)
    18442洪都拉斯西班牙语(洪都拉斯)ESH1252  (ANSI - 拉丁文 I)
    19466尼加拉瓜西班牙语(尼加拉瓜)ESI1252  (ANSI - 拉丁文 I)
    20490波多黎各(美)西班牙语(波多黎各(美))ESU1252  (ANSI - 拉丁文 I)

    LCID取决于语言,在表中列出国家名只是为了增加趣味性。例如可以看到以色列还在使用古老的希伯来语。“希伯来语”的法文是hébreu,这个单词还有一个意思,就是“不能理解的东西”。

     

    在winnt.h中,对subsystem的定义如下:
    #define IMAGE_SUBSYSTEM_UNKNOWN 0 // Unknown subsystem.
    #define IMAGE_SUBSYSTEM_NATIVE 1 // Image doesn’t require a subsystem.
    #define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Image runs in the Windows GUI subsystem.
    #define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Image runs in the Windows character subsystem.
    #define IMAGE_SUBSYSTEM_OS2_CUI 5 // image runs in the OS/2 character subsystem.
    #define IMAGE_SUBSYSTEM_POSIX_CUI 7 // image runs in the Posix character subsystem.
    #define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 // image is a native Win9x driver.
    #define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Image runs in the Windows CE subsystem.
    CUI就是Console UI了。我们使用的subsystem主要是3和2。

    在用户态看起来很底层的东西,例如Win32 subsystem的核心:kernel32.dll、user32.dll、gdi32.dll,基本上只是ntdll.dll的一个包装,而ntdll.dll包装了从用户态到核心态的system call,也称作“Native System Service”。
    用户态不能访问核心态的任何函数和变量,所以system call不同于一般的API调用。system call可以被看作:将要调用的功能ID放到eax,然后执行INT 2e。
    ntdll.dll通过system call使用核心态的ntoskrnl.exe和win32k.sys提供的功能。ntoskrnl.exe被尊称为“Executive”,可以看作是NT的大脑级模块。win32k.sys提供NT图形库接口的API。  

    展开全文
  • zz谈谈Windows程序中的字符编码

    千次阅读 2007-03-21 16:22:00
    谈谈Windows程序中的字符编码写这篇文章的起因是这么一个问题:我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹,这些数字似乎和字符集有关,但它们究竟是什么意思呢?...
  • 关于ANSI编码小结

    千次阅读 2015-04-15 16:15:41
    ANSI编码不是一个具体的字符集,而是对一些多字节字符集类型的字符集的一个总体的称呼,总称。 (Windows)ANSI编码中的每一个代码页值就是对一个具体的多字节字符集类型的字符集的别名。例如,代码页 936 就是...
  • ANSI编码和Unicode编码的不同

    万次阅读 热门讨论 2009-10-11 18:06:00
    ANSI编码最常见的应用就是在Windows当中的记事本程序中,当新建一个记事本,默认的保存编码格式就是ANSI,ANSI应该算是一种压缩编码了,当遇到标准的ASCII字符时,采用单字节表示,当遇到非标准的ASCII字符(如中文)...
  • 字符编码那些事--彻底理解掌握编码知识

    万次阅读 多人点赞 2020-05-04 16:42:33
    每一个程序员都不可避免的遇到字符编码的问题,很多人在字符编码方面同样遇到不少问题,而且一直对各种编码懵懵懂懂、不清不楚。这篇文章就是针对字符编码中的一些问题进行了详细的阐述,能从根本上理解字符编码
  • 字符编码的前世今生

    千次阅读 2017-09-18 17:24:07
    前言很多程序员对字符编码不太理解,虽然他们大概知道 ASCII、UTF8、GBK、Unicode 等术语概念,但在写代码过程中还是会遇到各种奇怪的编码问题,在 Java 中最常见的是乱码,而 Python 开发中遇到最多的是编码错误,...
  • 字符编码

    千次阅读 2013-03-08 15:28:15
    在下面的描述中,将以"中文"两个字为例,经查表可以知道其GB2312编码是"d6d0 cec4",Unicode编码为"4e2d 6587",UTF编码就是"e4b8ad e69687"。注意,这两个字没有iso8859-1编码,但可以用iso8859-1编码来"表示"。 2....
  • 关于Qt的字符编码问题

    千次阅读 2012-06-29 09:56:45
    在很多情况下,可能想要使用不同的编码方式来处理数据。例如大部分的日语文件都被存储在 Shift-JIS或者 ISO2022的文件中,而俄罗斯的用户常常使用KOI8-R或者CP1251编码方式。QT提供了一个QTextCodec类集合来从...
  •  问题一:为什么在用wsprintf输出Unicode编码的字符串时要先调用setlocale(LC_ALL, "chs");而对于printf输出多字节编码的字符串则有无setlocale均可正常输出。 如:  void main() { wchar_t* wstr = L"中文"; ...
  • 字符编码是每一位程序员都要面对的问题,尤其是在深入思考程序设计的细节时。这篇博文是我目前为止所见的文章中覆盖内容最系统而全面的一篇。其中,字符编码基本原理和各种主要编码类型的要点阐述到很精辟,值得一...
  • Java编码理解

    千次阅读 2013-03-23 11:53:53
    各类程序涉及的编码不外乎这些: 源代码文件的编码, 程序中字符串的编码, 程序执行输出显示时的编码. Java号称国际化的语言,是因为它的class文件采用UTF-8,而JVM运行时使用UTF-16。 摘自《谈谈对Java中...
  • 文件编码与字符编码

    千次阅读 2010-05-14 13:47:00
    文件编码与字符编码首先明确一点,文件不存在什么编码(归根结底文件都是二进制文件,用ue打开可以看到都是一个个的16进制数),只有文件中的字符才可以说编码编码与解码过程字符通过某种编码组织起来存到文件里面,...
  • C++中 Unicode 与 UTF-8 编码互转

    万次阅读 2016-11-17 22:44:21
    1、简述最近在发送网络请求时遇到了中文字符乱码的问题,在代码中调试字符正常,用抓包工具抓的包中文字符显示正常,就是发送到服务器就显示乱码了,那就要将客户端和服务器设置统一的编码(UTF-8),而我们程序中 ...
  • 四款好用的免费直播编码推流软件

    万次阅读 2018-04-14 21:40:43
    现代生活已经离不开互联网,离不开软件了。 这说法毫不夸张。 比如手机上各种 APP 眼花缭乱 ,各种...今天我们来谈谈 关于直播编码和推流的免费软件。 官网地址: http://www.800li.net/index.php?s=/home/websi...
  • ANSI编码和Unicode编码之深入理解

    千次阅读 2016-06-08 16:50:09
     ANSI编码最常见的应用就是在Windows当中的记事本程序中,当新建一个记事本,默认的保存编码格式就是ANSI,ANSI应该算是一种压缩编码了,当遇到标准的ASCII字符时,采用单字节表示,当遇到非标准的ASCII字符(如中文...
  • 文字编码

    千次阅读 2009-04-21 13:58:00
    比如在没有计算机之前,我们照样可以说法,照样可以在纸上写各种各样的文字,这些都是不需要什么编码的。字符编码编码是指把字符转换为数字形式以在计算机中存储和使用的过程。从编程的角度将就是将字符转换为一个...
  • 关于并发处理,下列哪些说法符合《阿里巴巴Java开发手册》:答案在文末 A. 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 B. 同步处理时,能锁部分代码区块的情况下不要锁整个方法;高并发时,...
  • 关于Unicode字符集 工程--设置--C/C++--预处理器,可以定义标识符,如UNICODE,_UNICODE,标识是按ASCII编译,还是按UNICODE编译 #include char定义全部 改成TCHAR,TCHAR根据设置不同定义为char或者...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 46,411
精华内容 18,564
关键字:

关于编码程序的说法