<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>FalconIA&#039;s BLOG &#187; 编码</title>
	<atom:link href="https://falconia.org/blog/archives/tag/coding/feed" rel="self" type="application/rss+xml" />
	<link>https://falconia.org/blog</link>
	<description>FalconIA&#039;s Lazy Blog</description>
	<lastBuildDate>Tue, 02 Dec 2014 01:45:41 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.2.38</generator>
	<item>
		<title>谈谈Windows程序中的字符编码</title>
		<link>https://falconia.org/blog/archives/2</link>
		<comments>https://falconia.org/blog/archives/2#comments</comments>
		<pubDate>Sun, 22 Jan 2006 21:52:20 +0000</pubDate>
		<dc:creator><![CDATA[FalconIA]]></dc:creator>
				<category><![CDATA[技术杂烩]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://falconia.no-ip.org/blog/?p=2</guid>
		<description><![CDATA[作　者：fmddlmyy 转载自：http://blog.csdn.net/fmddlmyy/archive/2005/06/21/399661.aspx 写这篇文章的起因是这么一个问题：我们在使用和安装Windows程序时，有时会看到以“2052”、“1033”这些数字为名的文件夹，这些数字似乎和字符集有关，但它们究竟是什么意思呢？ 研究这个问题的同时，又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等，一起经历这段琐碎细节为主，间或乐趣点缀的旅程。 0 Where is Win32 API Windows程序有用户态和核心态的说法。在32位地址空间中，0x80000000以下属于用户态，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为乐。 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><b>作　者：fmddlmyy<br />
转载自：<a href="http://blog.csdn.net/fmddlmyy/archive/2005/06/21/399661.aspx">http://blog.csdn.net/fmddlmyy/archive/2005/06/21/399661.aspx</a></b></p>
<p>写这篇文章的起因是这么一个问题：我们在使用和安装Windows程序时，有时会看到以“2052”、“1033”这些数字为名的文件夹，这些数字似乎和字符集有关，但它们究竟是什么意思呢？</p>
<p>研究这个问题的同时，又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等，一起经历这段琐碎细节为主，间或乐趣点缀的旅程。<br />
<span id="more-2"></span><br />
0 Where is Win32 API<br />
Windows程序有用户态和核心态的说法。在32位地址空间中，0x80000000以下属于用户态，0x80000000以上属于核心态。所有硬件管理都在核心态。用户态程序的不能直接使用核心态的任何代码。所谓核心态其实只是CPU的一种保护模式。在x86 CPU上，用户态处于ring 3，核心态处于ring 0。</p>
<p>从用户态进入核心态的最常用的方法是在寄存器eax填一个功能码，然后执行int 2e。这有点像DOS时代的DOS和BIOS系统调用。在NT架构中这种机制被称作system service。 </p>
<p>在核心态提供system service的有两个家伙：ntoskrnl.exe和win32k.sys。ntoskrnl.exe是Windows的大脑，它的上层被称为Executive，下层被称作Kernel。Win32k.sys提供与显示有关的system service。</p>
<p>在用户态一侧，有一个重要的角色叫作ntdll.dll，大多数system service都是它调用的。它封装这些system service，然后提供一个API接口。这个接口被称作native API。 native API的用户是各个子系统（subsystem），包括Win32子系统、OS/2子系统、POSIX子系统。各个子系统为Win32、OS2、POSIX程序提供了运行平台。</p>
<p>ntdll.dll由于提供了平台无关的API接口，所以被看作是NT系统的原生接口，由之得到了“native API”的匪号。其实它的主要工作是将调用传递到核心态。</p>
<p>Win32、OS/2、POSIX，听起来很庞大。其实真正做好的只有Win32子系统。OS2、POSIX都是Console UI，即只有字符界面。提供OS/2子系统，只因为在1988年，NT的主要设计目标就是与OS/2兼容，后来由于Windows 3.0卖得很好，所以设计目标被变更为与Windows兼容。提供POSIX子系统，是为了应付美国政府的一个编号为FIPS 151-2的标准。</p>
<p>Win32子系统的管理员是一个叫作csrss.exe的弟兄，它的全名是：Client/Server Run-Time Subsystem。它刚上任时，本来要分管所有的子系统，但后来POSIX和OS/2都被分别处理了，所以只管了一个Win32。即使这样也很了不起，所有的Win32程序的进程、线程们都要向它登记。</p>
<p>不过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。</p>
<p>附录0 Windows的启动<br />
计算机上电后，从BIOS的ROM开始运行。BIOS在做一些初始化后会将硬盘的第一个扇区的数据读入内存，然后将控制权交给它，这段数据被称作Master Boot Record（MBR）。</p>
<p>MBR包含一段启动代码和硬盘的主分区表。这段启动代码扫描主分区表，找到第一个可以启动的分区，然后将这个分区的第一个扇区读入内存并运行。这个扇区被称作引导扇区（boot sector）。</p>
<p>引导扇区的代码具备读文件系统根目录的能力，显然不同的文件系统需要不同的代码。引导扇区会从根目录中读出一个叫作ntldr的文件。顾名思义，这个文件是load NT的主要角色。它的业绩主要包括将CPU从实模式转入保护模式，启动分页机制，处理boot.ini等。</p>
<p>如果boot.ini中有一句：</p>
<p>C:bootsect.rh="Red Hat Linux"</p>
<p>bootsect.rh的内容是Linux引导扇区，用户又选择了“Red Hat Linux”，ntldr就会将执行Linux的引导扇区，开始Linux的引导。如果用户选择继续使用Windows，ntldr会装载并运行我们前面提到的ntoskrnl.exe。</p>
<p>ntoskrnl.exe会启动会话管理器smss.exe。smss.exe启动csrss.exe和winlogon.exe。smss.exe会永远等待csrss.exe和winlogon.exe返回。如果两者之一异常中止，就会导致系统崩溃。所以病毒们经常以打击csrss.exe为乐。</p>
<p>winlogon.exe负责用户登录，在完成登录后，它会启动注册表HKLMSOFTWAREMicrosoftWindows NTCurrent VersionWinlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置，让硬盘响个不停，并考验我们的耐性，最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后，我们就会看到熟悉的开始菜单和桌面。</p>
<p>1 Win32 API的A/W函数<br />
要了解Win32子系统的DLL们提供了哪些API，最直接的方法就是用Win32dsm直接查看DLL们的导出表。这时我们会发现Win32 API中带字符串的API一般都有两个版本，例如CreateFileA和CreateFileW。当然也有例外，例如GetProcAddress函数。</p>
<p>A代表ANSI代码页，W是宽字符，即Unicode字符。Windows中的Unicode字符一般指UCS2的UTF16-LE编码。让我们通过几个实例观察A/W版本间的关系。</p>
<p>例1：用WIn32dsm查看gdi32.dll的汇编代码，可以看到TextOutA调用GdiGetCodePage获取当前代码页，再调用MultiByteToWideChar转换输入的字符串，然后调用一个内部函数。而TextOutW直接调用这个内部函数。</p>
<p>例2：用调试器跟踪一个使用了CreateFileA的程序，可以看到：CreateFileA在将输入字符串转换为Unicode后，会调用CreateFileW。假设输入文件名是“测试.txt”，对应的数据就是：“B2 E2 CA D4 2E 74 78 74 00”。<br />
在调试器中可以看到传给CreateFileW的文件名数据是：“4B 6D D5 8B 2E 00 74 00 78 00 74 00 00 00”。 这是"测试.txt"对应的Unicdoe字符串。CreateFileW会接着调用ntdll.dll中的NtCreateFile。顺便看看NtCreateFile的代码：<br />
mov eax, 00000020<br />
lea edx, dword ptr [esp+04]<br />
int 2E<br />
ret 002C<br />
可见这个native API只是简单地调用了核心态提供的0x20号system service。</p>
<p>例3：gdi32.dll中的GetGlyphOutline函数可以获取指定字符的字模。GetGlyphOutlineA和GetGlyphOutlineW函数都会调用同一个内部函数（记作F）。函数F在返回前将通过int 2E调用0x10B1号system service。<br />
GetGlyphOutlineW直接调用函数F。GetGlyphOutlineA在调用函数F前，要依次调用GdiGetCodePage、IsDBCSLeadByteEx和MultiByteToWideChar，将当前代码页的字符编码转换成Unicode编码。<br />
如果我们调用GetGlyphOutlineA时传入“baba”，这是“汉”字的GBK编码，用调试器可以看到传给函数F的字符编码是“6c49”，这是“汉”字的Unicode编码。</p>
<p>从以上例子可见，A版本总会在某处将输入的字符串转换为Unicode字符串，然后和W版本执行相同的代码。在由A/W版本API引出MBCS程序和Unicode程序前，让我们先解释一下Locale和ANSI代码页。</p>
<p>2 Locale和ANSI代码页<br />
2.1 Locale和LCID<br />
Locale是指特定于某个国家或地区的一组设定，包括字符集，数字、货币、时间和日期的格式等。在Windows中，每个Locale可以用一个32位数字表示，记作LCID。在winnt.h中可以看到LCID的组成。它的高16位表示字符的排序方法，一般为0。在它的低16位中，低10位是primary language的ID，高4位指定sublanguage。sublanguage被用来区分同一种语言的不同编码。下面是部分primary language和sublanguage的常数定义：</p>
<p>#define LANG_CHINESE 0x04<br />
#define LANG_ENGLISH 0x09<br />
#define LANG_FRENCH 0x0c<br />
#define LANG_GERMAN 0x07</p>
<p>#define SUBLANG_CHINESE_TRADITIONAL 0x01 // Chinese (Taiwan Region)<br />
#define SUBLANG_CHINESE_SIMPLIFIED 0x02 // Chinese (PR China)<br />
#define SUBLANG_ENGLISH_US 0x01 // English (USA)<br />
#define SUBLANG_ENGLISH_UK 0x02 // English (UK)</p>
<p>好，现在我们可以计算简体中文的LCID了，将sublanguage的常数左移10位，即乘上1024，再加上primary language的常数：2*1024+4=2052，16进制是0804。美国英语是：1*1024+9=1033，16进制是0409。。繁体中文是1*1024+4=1028，16进制是0404。</p>
<p>2.2 代码页<br />
每个Locale都联系着很多信息，可以通过GetLocalInfo函数读取。其中最重要的信息就是字符集了，即Locale对应的语言文字的编码。Windows将字符集称作代码页。</p>
<p>每个Locale可以对应一个ANSI代码页和一个OEM代码页。Win32 API使用ANSI代码页，底层设备使用OEM代码页，两者可以相互映射。</p>
<p>例如English (US)的ANSI和OEM代码页分别为“1252 (ANSI - Latin I)”和“437 (OEM - United States)”。 Chinese (PRC)的ANSI和OEM代码页都是“936 (ANSI/OEM - Simplified Chinese GBK)”。 &nbsp;Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM - Traditional Chinese Big5)”。</p>
<p>附录1中有一张很长的表。列出了我正在使用的Windows所支持的135个Locale的部分信息，包括 LCID、国家/地区名称、语言名称、语言缩写和对应的ANSI代码页。</p>
<p>2.3 系统Locale、用户Locale，再谈ANSI代码页<br />
在Windows中，通过控制面板可以为系统和用户分别设置Locale。系统Locale决定代码页，用户Locale决定数字、货币、时间和日期的格式。这不是一个好的设计，后面会谈到它带来的问题。</p>
<p>使用GetSystemDefaultLCID函数和GetUserDefaultLCID函数分别得到系统和用户的LCID。有很多材料将这两个函数和另外两个函数混淆：GetSystemDefaultUILanguage和GetUserDefaultUILanguage。</p>
<p>GetSystemDefaultUILanguage和GetUserDefaultUILanguage得到的是您当前使用的Windows版本所带的UI资源的语言。</p>
<p>用户程序缺省使用的代码页是当前系统Locale的ANSI代码页，可以称作ANSI编码，也就是A版本的Win32 API默认的字符编码。对于一个未指定编码方式的文本文件，Windows会按照ANSI编码解释。</p>
<p>2.4 AppLocale<br />
如果一个文本文件采用BIG5编码，系统当前的ANSI代码页是GBK。打开这个文件，就会显示乱码。例如“中文”在BIG5中的编码是A4A4、A4E5，这两个编码在GBK中对应的字符是“いゅ”。这是日文的两个平假名。</p>
<p>在Windows XP平台有一个AppLocale程序，可以以指定的语言运行非Unicode程序。用Win32dsm打开看一看，其实它只是在运行程序前设置了两个环境变量。我们可以用个批处理文件模仿一下：</p>
<p>@ECHO OFF<br />
SET __COMPAT_LAYER=#ApplicationLocale<br />
SET ApplocaleID=0404<br />
start notepad.exe</p>
<p>在简体中文平台，用这个批处理文件启动的记事本可以正确显示BIG5编码的文本文件。用它打开GBK编码的文本文件会怎么样？“中文”会被显示为“笢恅”。设置这两个环境变量会作用于当前进程和其子进程。Windows 2000平台不支持这个方法。</p>
<p>3 MBCS程序和Unicode程序<br />
3.1 与字符编码有关的编译参数<br />
让我们回到Win32 API。我们在程序中使用的Win32 API没有A/W后缀，Windows的头文件会根据编译参数UNICODE将没有后缀的函数名替换为A版本或W版本，例如：</p>
<p>#ifdef UNICODE<br />
#define CreateFile CreateFileW<br />
#else<br />
#define CreateFile CreateFileA<br />
#endif</p>
<p>C RunTime库（CRT）使用_UNICODE和_MBCS来区分三套字符串处理函数，分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen ，猜猜以下函数返回几？</p>
<p>strlen("VOIP网关");<br />
_mbslen((unsigned char *)"VOIP网关");<br />
wcslen(L"VOIP网关");</p>
<p>答案是8、6、6。L"ANSI字符串"通知编译器将ANSI字符串转换为Unicode字符串，这是VC++编译器提供的一个小甜点。不过我们应该用宏：_T("ANSI字符串")。_T宏只在我们定义了_UNICODE时才转换。这样同一套代码既可以编译MBCS版本，也可以编译Unicode版本。 </p>
<p>MFC用_UNICODE参数区分Unicode版本特有的代码，决定使用什么版本的导入库或静态库。 </p>
<p>3.2 Unicode程序、MBCS程序和多语言支持<br />
Unicode程序直接使用Unicode版本的CRT和Win32 API。Unicode程序的运行与当前的ANSI代码页没有关系。MBCS程序的运行依赖于ANSI代码页。如果设计者和使用者使用不同的代码页，就可能出现乱码。微软开发的程序大都是Unicode程序，不管我们怎样变换系统Locale，它们总能正常运行。</p>
<p>使用VCL类库的Delphi程序都是MBCS程序。VCL框架在程序启动会调用GetThreadLocale获取当前用户的LCID，然后在当前目录查找对应的资源文件，命名规则是：程序名+&#39;.&#39;+语言缩写，语言缩写可以参见附录1。在找不到时才会使用EXE文件中的资源。不过如果系统LCID是English(United States)，用户LCID是Chinese(PRC)，由VCL产生的程序就会出现乱码。读者可以自己分析原因。</p>
<p>为VCL程序做多语言版本。只要用Delphi自带的Resource DLL Wizard再做一个特定语言的资源DLL，原来的程序都不用改。不过很多程序员用其它组件做多语言版本，例如TsiLang 。</p>
<p>MBCS程序虽然也可以做成多语言版本，但它无法在同时显示不同代码页特有的字符，这时就必须使用Unicode程序了。</p>
<p>VS.NET文档中有个多语言资源的例子：SatDLL。它只用Win32 API的例子，却用了VC7项目。我在学习时将它改成了VC6项目，并纠正了它的两个问题：<br />
1、用GetUserDefaultUILanguage读到的是Windows资源版本，不是当前用户设置的代码页。<br />
2、启动时没有使用资源DLL里的菜单。</p>
<p>在我的个人主页(http://fmddlmyy.home4u.china.com)上可以下载修改过的SatDLL。这个程序说明了支持多语言资源的基本思路：将不同语言资源放到不同的DLL中，在程序启动时根据当前Locale装载对应的资源DLL。必要时动态切换资源。为了标记不同语言的资源，可以将它们放到不同的目录中，以LCID作为目录名，例如“2052”、“1033”。当然我们也可以用其它方法联系LCID和资源DLL。</p>
<p>MFC程序可以在App类的InitInstance函数中用AfxSetResourceHandle函数设置资源DLL。在Delphi中动态切换资源可以参考Delphi Demo目录RichEdit项目的ReInit.pas。在读取当前设定时，建议用GetSystemDefaultLCID函数，因为系统Locale决定ANSI代码页。</p>
<p>3.4 资源和乱码<br />
通过检查可执行文件，我们可以确定VC和Delphi的资源编译器都以Unicode保存字符资源。在VC环境编辑资源时，我们会指定资源的代码页。编译器根据资源的代码页，将其转换到Unicode。</p>
<p>Unicode程序直接使用以Unicode编码保存的资源。MBCS程序需要将Unicode资源先转换回当前ANSI代码页，然后再使用。如果资源中的Unicode字符串不能映射到当前代码页中的字符，就会出现??。</p>
<p>例如Windows的标准对话框也会出现乱码。假设我们使用简体中文Windows，当前Locale是Chinese (TW)，我们的程序是MBCS的，使用标准的打开文件对话框。因为在BIG5中没有“开”这个字，所以“打开”会被显示成“打?”。将程序编译成Unicode版本，就可以避免这个问题。</p>
<p>如果字符不是保存在资源中，而是硬编码在程序中。然后开发者和用户使用不同的代码页，就会导致乱码。假设开发者的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”。</p>
<p>回答我前面提过的一个问题：Delphi程序根据用户LCID转换资源中的字符串。如果用户LCID是Chinese (PRC)，系统LCID是English (US)。那么资源中的Unicode字符串会被转换为GBK编码，然后按照Latin I显示，这时我们看到的就是类似“???t”的东东，不是??。</p>
<p>既然资源是以Unicode保存的，MBCS程序如果不将其转换到ANSI代码页，而用W版本的函数直接显示，就不会产生乱码。例如MFC程序菜单里的中文，在English (US)的Locale也可以正常显示。不过这取决于各部分代码的具体实现，menu bar控件里的中文在English (US)的Locale会全部显示成??。</p>
<p>进一步的参考资料<br />
本文的第0节和附录0主要参考了《Inside Windows 2000 Third Edition》，国内出过该书的影印版。DDK文档中有大量Windows内核的信息。用Win32dsm和各种调试器查看Windows系统文件可以获得更直接的信息。</p>
<p>关于Window程序的字符编码，最好的参考资料是winnt.h等SDK的包含文件、VCL、MFC、CRT的源文件。我们不需要阅读它们，只要找到自己感兴趣的信息就可以了，用Source Insight可能方便一些。</p>
<p>本文所谈的不是什么万古不迁的道理，只是别的程序员的一些设定，我们因为需要使用他们的程序，所以有必要了解一些细节。研究问题的方法和兴趣永远比问题本身重要，如一句拉丁俗语所说：res, non verba，实质胜于文字。</p>
<p>尾声<br />
“明月虽有圆缺，但毕竟永恒不灭，人生却如过眼烟云，一去不回，真不知计较为何？”</p>
<p>“蛙声虽是短促，但却是万籁中一个活泼的禅机，也可以说万古如斯，永恒不迁，无奈感受到的，能有几人？”</p>
<p>这是一本武侠书中的对话。在时间的长河中，人生和蛙声一样易逝。说到蛙声，我的20个月的小宝宝在喝汤后，略加酝酿，就会紧闭着嘴巴，发出很像蛙鸣的声音。我们会逗他说：“小青蛙又来了”。小家伙益发得意，不管我的抗议，将连汤带油的小下巴亲热地贴在我的身上。</p>
<p>附录1 一些关于LCID的信息<br />
使用EnumSystemLocales函数可以枚举系统支持的LCID。用GetLocaleInfo可以得到ANSI代码页的ID，再通过GetCPInfoEx可以获得代码页的全称。以下是我在中文Windows XP上读到的内容。 </p>
<p>LCID<br />
 国家或地区<br />
 语言<br />
 语言缩写<br />
 ANSI代码页</p>
<p>1025<br />
 沙特阿拉伯<br />
 阿拉伯语(沙特阿拉伯)<br />
 ARA<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>1026<br />
 保加利亚<br />
 保加利亚语<br />
 BGR<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1027<br />
 西班牙<br />
 加泰隆语<br />
 CAT<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1028<br />
 台湾<br />
 中文(台湾)<br />
 CHT<br />
 950 &nbsp; (ANSI/OEM - 繁体中文 Big5)</p>
<p>1029<br />
 捷克共和国<br />
 捷克语<br />
 CSY<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1030<br />
 丹麦<br />
 丹麦语<br />
 DAN<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1031<br />
 德国<br />
 德语(德国)<br />
 DEU<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1032<br />
 希腊<br />
 希腊语<br />
 ELL<br />
 1253 &nbsp;(ANSI - 希腊文)</p>
<p>1033<br />
 美国<br />
 英语(美国)<br />
 ENU<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1034<br />
 西班牙<br />
 西班牙语(传统)<br />
 ESP<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1035<br />
 芬兰<br />
 芬兰语<br />
 FIN<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1036<br />
 法国<br />
 法语(法国)<br />
 FRA<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1037<br />
 以色列<br />
 希伯来语<br />
 HEB<br />
 1255 &nbsp;(ANSI - 希伯来文)</p>
<p>1038<br />
 匈牙利<br />
 匈牙利语<br />
 HUN<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1039<br />
 冰岛<br />
 冰岛语<br />
 ISL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1040<br />
 意大利<br />
 意大利语(意大利)<br />
 ITA<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1041<br />
 日本<br />
 日语<br />
 JPN<br />
 932 &nbsp; (ANSI/OEM - 日文 Shift-JIS)</p>
<p>1042<br />
 朝鲜<br />
 朝鲜语<br />
 KOR<br />
 949 &nbsp; (ANSI/OEM - 韩文)</p>
<p>1043<br />
 荷兰<br />
 荷兰语(荷兰)<br />
 NLD<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1044<br />
 挪威<br />
 挪威语(伯克梅尔)<br />
 NOR<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1045<br />
 波兰<br />
 波兰语<br />
 PLK<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1046<br />
 巴西<br />
 葡萄牙语(巴西)<br />
 PTB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1048<br />
 罗马尼亚<br />
 罗马尼亚语<br />
 ROM<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1049<br />
 俄罗斯<br />
 俄语<br />
 RUS<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1050<br />
 克罗地亚<br />
 克罗地亚语<br />
 HRV<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1051<br />
 斯洛伐克语<br />
 斯洛伐克语<br />
 SKY<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1052<br />
 阿尔巴尼亚<br />
 阿尔巴尼亚语<br />
 SQI<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1053<br />
 瑞典<br />
 瑞典语<br />
 SVE<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1054<br />
 泰国<br />
 泰语<br />
 THA<br />
 874 &nbsp; (ANSI/OEM - 泰文)</p>
<p>1055<br />
 土耳其<br />
 土耳其语<br />
 TRK<br />
 1254 &nbsp;(ANSI - 土耳其文)</p>
<p>1056<br />
 巴基斯坦伊斯兰共和国<br />
 乌都语<br />
 URD<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>1057<br />
 印度尼西亚<br />
 印度尼西亚语<br />
 IND<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1058<br />
 乌克兰<br />
 乌克兰语<br />
 UKR<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1059<br />
 比利时<br />
 比利时语<br />
 BEL<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1060<br />
 斯洛文尼亚<br />
 斯洛文尼亚语<br />
 SLV<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>1061<br />
 爱沙尼亚<br />
 爱沙尼亚语<br />
 ETI<br />
 1257 &nbsp;(ANSI - 波罗的海文)</p>
<p>1062<br />
 拉脱维亚<br />
 拉脱维亚语<br />
 LVI<br />
 1257 &nbsp;(ANSI - 波罗的海文)</p>
<p>1063<br />
 立陶宛<br />
 立陶宛语<br />
 LTH<br />
 1257 &nbsp;(ANSI - 波罗的海文)</p>
<p>1065<br />
 伊朗<br />
 法斯语<br />
 FAR<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>1066<br />
 越南<br />
 越南语<br />
 VIT<br />
 1258 &nbsp;(ANSI/OEM - 越南)</p>
<p>1067<br />
 亚美尼亚<br />
 亚美尼亚语<br />
 HYE<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1068<br />
 阿塞拜疆<br />
 阿塞拜疆语(拉丁文)<br />
 AZE<br />
 1254 &nbsp;(ANSI - 土耳其文)</p>
<p>1069<br />
 西班牙<br />
 巴士克语<br />
 EUQ<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1071<br />
 前南斯拉夫马其顿共和国<br />
 马其顿语(FYROM)<br />
 MKI<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1078<br />
 南非<br />
 南非语<br />
 AFK<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1079<br />
 格鲁吉亚<br />
 格鲁吉亚语<br />
 KAT<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1080<br />
 法罗群岛<br />
 法罗语<br />
 FOS<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1081<br />
 印度<br />
 印地语<br />
 HIN<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1086<br />
 马来西亚<br />
 马来语(马来西亚)<br />
 MSL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1087<br />
 吉尔吉斯坦<br />
 哈萨克语<br />
 KKZ<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1088<br />
 吉尔吉斯斯坦<br />
 吉尔吉斯语 (西里尔文)<br />
 KYR<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1089<br />
 肯尼亚<br />
 斯瓦希里语<br />
 SWK<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1091<br />
 乌兹别克斯坦<br />
 乌兹别克语(拉丁文)<br />
 UZB<br />
 1254 &nbsp;(ANSI - 土耳其文)</p>
<p>1092<br />
 鞑靼斯坦<br />
 鞑靼语<br />
 TTT<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1094<br />
 印度<br />
 旁遮普语<br />
 PAN<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1095<br />
 印度<br />
 古吉拉特语<br />
 GUJ<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1097<br />
 印度<br />
 泰米尔语<br />
 TAM<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1098<br />
 印度<br />
 泰卢固语<br />
 TEL<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1099<br />
 印度<br />
 卡纳拉语<br />
 KAN<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1102<br />
 印度<br />
 马拉地语<br />
 MAR<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1103<br />
 印度<br />
 梵文<br />
 SAN<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1104<br />
 蒙古<br />
 蒙古语(西里尔文)<br />
 MON<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>1110<br />
 西班牙<br />
 加里西亚语<br />
 GLC<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>1111<br />
 印度<br />
 孔卡尼语<br />
 KNK<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1114<br />
 叙利亚<br />
 叙利亚语<br />
 SYR<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>1125<br />
 马尔代夫<br />
 第维埃语<br />
 DIV<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>2049<br />
 伊拉克<br />
 阿拉伯语(伊拉克)<br />
 ARI<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>2052<br />
 中华人民共和国<br />
 中文(中国)<br />
 CHS<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>2055<br />
 瑞士<br />
 德语(瑞士)<br />
 DES<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2057<br />
 英国<br />
 英语(英国)<br />
 ENG<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2058<br />
 墨西哥<br />
 西班牙语(墨西哥)<br />
 ESM<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2060<br />
 比利时<br />
 法语(比利时)<br />
 FRB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2064<br />
 瑞士<br />
 意大利语(瑞士)<br />
 ITS<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2067<br />
 比利时<br />
 荷兰语(比利时)<br />
 NLB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2068<br />
 挪威<br />
 挪威语(尼诺斯克)<br />
 NON<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2070<br />
 葡萄牙<br />
 葡萄牙语(葡萄牙)<br />
 PTG<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2074<br />
 塞尔维亚<br />
 塞尔维亚语(拉丁文)<br />
 SRL<br />
 1250 &nbsp;(ANSI - 中欧)</p>
<p>2077<br />
 芬兰<br />
 瑞典语(芬兰)<br />
 SVF<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2092<br />
 阿塞拜疆<br />
 阿塞拜疆语(西里尔文)<br />
 AZE<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>2110<br />
 文莱达鲁萨兰<br />
 马来语(文莱达鲁萨兰)<br />
 MSB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>2115<br />
 乌兹别克斯坦<br />
 乌兹别克语(西里尔文)<br />
 UZB<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>3073<br />
 埃及<br />
 阿拉伯语(埃及)<br />
 ARE<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>3076<br />
 香港特别行政区<br />
 中文(香港特别行政区)<br />
 ZHH<br />
 950 &nbsp; (ANSI/OEM - 繁体中文 Big5)</p>
<p>3079<br />
 奥地利<br />
 德语(奥地利)<br />
 DEA<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>3081<br />
 澳大利亚<br />
 英语(澳大利亚)<br />
 ENA<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>3082<br />
 西班牙<br />
 西班牙语(国际)<br />
 ESN<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>3084<br />
 加拿大<br />
 法语(加拿大)<br />
 FRC<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>3098<br />
 塞尔维亚<br />
 塞尔维亚语(西里尔文)<br />
 SRB<br />
 1251 &nbsp;(ANSI - 西里尔文)</p>
<p>4097<br />
 利比亚<br />
 阿拉伯语(利比亚)<br />
 ARL<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>4100<br />
 新加坡<br />
 中文(新加坡)<br />
 ZHI<br />
 936 &nbsp; (ANSI/OEM - 简体中文 GBK)</p>
<p>4103<br />
 卢森堡<br />
 德语(卢森堡)<br />
 DEL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>4105<br />
 加拿大<br />
 英语(加拿大)<br />
 ENC<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>4106<br />
 危地马拉<br />
 西班牙语(危地马拉)<br />
 ESG<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>4108<br />
 瑞士<br />
 法语(瑞士)<br />
 FRS<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>5121<br />
 阿尔及利亚<br />
 阿拉伯语(阿尔及利亚)<br />
 ARG<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>5124<br />
 澳门特别行政区<br />
 中文(澳门特别行政区)<br />
 ZHM<br />
 950 &nbsp; (ANSI/OEM - 繁体中文 Big5)</p>
<p>5127<br />
 列支敦士登<br />
 德语(列支敦士登)<br />
 DEC<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>5129<br />
 新西兰<br />
 英语(新西兰)<br />
 ENZ<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>5130<br />
 哥斯达黎加<br />
 西班牙语(哥斯达黎加)<br />
 ESC<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>5132<br />
 卢森堡<br />
 法语(卢森堡)<br />
 FRL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>6145<br />
 摩洛哥<br />
 阿拉伯语(摩洛哥)<br />
 ARM<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>6153<br />
 爱尔兰<br />
 英语(爱尔兰)<br />
 ENI<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>6154<br />
 巴拿马<br />
 西班牙语(巴拿马)<br />
 ESA<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>6156<br />
 摩纳哥公国<br />
 法语(摩纳哥)<br />
 FRM<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>7169<br />
 突尼斯<br />
 阿拉伯语(突尼斯)<br />
 ART<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>7177<br />
 南非<br />
 英语(南非)<br />
 ENS<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>7178<br />
 多米尼加共和国<br />
 西班牙语(多米尼加共和国)<br />
 ESD<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>8193<br />
 阿曼<br />
 阿拉伯语(阿曼)<br />
 ARO<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>8201<br />
 牙买加<br />
 英语(牙买加)<br />
 ENJ<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>8202<br />
 委内瑞拉<br />
 西班牙语(委内瑞拉)<br />
 ESV<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>9217<br />
 也门<br />
 阿拉伯语(也门)<br />
 ARY<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>9225<br />
 加勒比海<br />
 英语(加勒比海)<br />
 ENB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>9226<br />
 哥伦比亚<br />
 西班牙语(哥伦比亚)<br />
 ESO<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>10241<br />
 叙利亚<br />
 阿拉伯语(叙利亚)<br />
 ARS<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>10249<br />
 伯利兹<br />
 英语(伯利兹)<br />
 ENL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>10250<br />
 秘鲁<br />
 西班牙语(秘鲁)<br />
 ESR<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>11265<br />
 约旦<br />
 阿拉伯语(约旦)<br />
 ARJ<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>11273<br />
 特立尼达和多巴哥<br />
 英语(特立尼达)<br />
 ENT<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>11274<br />
 阿根廷<br />
 西班牙语(阿根廷)<br />
 ESS<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>12289<br />
 黎巴嫩<br />
 阿拉伯语(黎巴嫩)<br />
 ARB<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>12297<br />
 津巴布韦<br />
 英语(津巴布韦)<br />
 ENW<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>12298<br />
 厄瓜多尔<br />
 西班牙语(厄瓜多尔)<br />
 ESF<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>13313<br />
 科威特<br />
 阿拉伯语(科威特)<br />
 ARK<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>13321<br />
 菲律宾共和国<br />
 英语(菲律宾)<br />
 ENP<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>13322<br />
 智利<br />
 西班牙语(智利)<br />
 ESL<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>14337<br />
 阿联酋<br />
 阿拉伯语(阿联酋)<br />
 ARU<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>14346<br />
 乌拉圭<br />
 西班牙语(乌拉圭)<br />
 ESY<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>15361<br />
 巴林<br />
 阿拉伯语(巴林)<br />
 ARH<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>15370<br />
 巴拉圭<br />
 西班牙语(巴拉圭)<br />
 ESZ<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>16385<br />
 卡塔尔<br />
 阿拉伯语(卡塔尔)<br />
 ARQ<br />
 1256 &nbsp;(ANSI - 阿拉伯文)</p>
<p>16394<br />
 玻利维亚<br />
 西班牙语(玻利维亚)<br />
 ESB<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>17418<br />
 萨尔瓦多<br />
 西班牙语(萨尔瓦多)<br />
 ESE<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>18442<br />
 洪都拉斯<br />
 西班牙语(洪都拉斯)<br />
 ESH<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>19466<br />
 尼加拉瓜<br />
 西班牙语(尼加拉瓜)<br />
 ESI<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>20490<br />
 波多黎各(美)<br />
 西班牙语(波多黎各(美))<br />
 ESU<br />
 1252 &nbsp;(ANSI - 拉丁文 I)</p>
<p>LCID取决于语言，在表中列出国家名只是为了增加趣味性。例如可以看到以色列还在使用古老的希伯来语。“希伯来语”的法文是hébreu，这个单词还有一个意思，就是“不能理解的东西”。</p>
]]></content:encoded>
			<wfw:commentRss>https://falconia.org/blog/archives/2/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>谈谈Unicode编码，简要解释UCS、UTF、BMP、BOM等名词</title>
		<link>https://falconia.org/blog/archives/1</link>
		<comments>https://falconia.org/blog/archives/1#comments</comments>
		<pubDate>Sun, 22 Jan 2006 21:42:21 +0000</pubDate>
		<dc:creator><![CDATA[FalconIA]]></dc:creator>
				<category><![CDATA[技术杂烩]]></category>
		<category><![CDATA[编码]]></category>
		<category><![CDATA[编程]]></category>

		<guid isPermaLink="false">http://falconia.no-ip.org/blog/?p=1</guid>
		<description><![CDATA[作　者：fmddlmyy 转载自：http://blog.csdn.net/fmddlmyy/archive/2005/05/04/372148.aspx 这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念，增进知识，类似于打RPG游戏的升级。整理这篇文章的动机是两个问题： 问题一： 使用Windows记事本的“另存为”，可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件，Windows是怎样识别编码方式的呢？ 我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节，分别是FF、FE（Unicode）,FE、FF（Unicode big endian）,EF、BB、BF（UTF-8）。但这些标记是基于什么标准呢？ 问题二： 最近在网上看到一个ConvertUTF.c，实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式，我原来就了解。但这个程序让我有些糊涂，想不起来UTF-16和UCS2有什么关系。 查了查相关资料，总算将这些问题弄清楚了，顺带也了解了一些Unicode的细节。写成一篇文章，送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂，但要求读者知道什么是字节，什么是十六进制。 0、big endian和little endian big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时，究竟是将6C写在前面，还是将49写在前面？如果将6C写在前面，就是big endian。还是将49写在前面，就是little endian。 “endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开，由此曾发生过六次叛乱，其中一个皇帝送了命，另一个丢了王位。 我们一般将endian翻译成“字节序”，将big endian和little endian称作“大尾”和“小尾”。 1、字符编码、内码，顺带介绍汉字编码 字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码，为了处理汉字，程序员设计了用于简体中文的GB2312和用于繁体中文的big5。 GB2312(1980年)一共收录了7445个字符，包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7，低字节从A1-FE，占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号，它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字，同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030，对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。 从ASCII、GB2312、GBK到GB18030，这些编码方法是向下兼容的，即同一个字符在这些方案中总是有相同的编码，后面的标准支持更多的字符。在这些编码中，英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼，GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。 有的中文Windows的缺省内码还是GBK，可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符，普通人是很难用到的，通常我们还是用GBK指代中文Windows内码。 这里还有一些细节： GB2312的原文还是区位码，从区位码到内码，需要在高字节和低字节上分别加上A0。 在DBCS中，GB内码的存储格式始终是big endian，即高位在前。 GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析：在读取DBCS字符流时，只要遇到高位为1的字节，就可以将下两个字节作为一个双字节编码，而不用管低字节的高位是什么。 2、Unicode、UCS和UTF 前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容（更准确地说，是与ISO-8859-1兼容），与GB码不兼容。例如“汉”字的Unicode编码是6C49，而GB码是BABA。 Unicode也是一种字符编码方法，不过它是由国际组织设计，可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set"，简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。 根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载：历史上存在两个试图独立设计Unicode的组织，即国际标准化组织（ISO）和一个软件制造商的协会（unicode.org）。ISO开发了ISO 10646项目，Unicode协会开发了Unicode项目。 在1991年前后，双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果，并为创立一个单一编码表而协同工作。从Unicode2.0开始，Unicode项目采用了与ISO 10646-1相同的字库和字码。 目前两个项目仍都存在，并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。 UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码，是由UTF(UCS [&#8230;]]]></description>
				<content:encoded><![CDATA[<p><b>作　者：fmddlmyy<br />
转载自：<a href="http://blog.csdn.net/fmddlmyy/archive/2005/05/04/372148.aspx">http://blog.csdn.net/fmddlmyy/archive/2005/05/04/372148.aspx</a></b></p>
<p>这是一篇程序员写给程序员的趣味读物。所谓趣味是指可以比较轻松地了解一些原来不清楚的概念，增进知识，类似于打RPG游戏的升级。整理这篇文章的动机是两个问题：</p>
<p>问题一：<br />
使用Windows记事本的“另存为”，可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件，Windows是怎样识别编码方式的呢？</p>
<p>我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节，分别是FF、FE（Unicode）,FE、FF（Unicode big endian）,EF、BB、BF（UTF-8）。但这些标记是基于什么标准呢？</p>
<p>问题二：<br />
最近在网上看到一个ConvertUTF.c，实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode(UCS2)、GBK、UTF-8这些编码方式，我原来就了解。但这个程序让我有些糊涂，想不起来UTF-16和UCS2有什么关系。<br />
查了查相关资料，总算将这些问题弄清楚了，顺带也了解了一些Unicode的细节。写成一篇文章，送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂，但要求读者知道什么是字节，什么是十六进制。<br />
<span id="more-1"></span><br />
0、big endian和little endian<br />
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时，究竟是将6C写在前面，还是将49写在前面？如果将6C写在前面，就是big endian。还是将49写在前面，就是little endian。</p>
<p>“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开，由此曾发生过六次叛乱，其中一个皇帝送了命，另一个丢了王位。</p>
<p>我们一般将endian翻译成“字节序”，将big endian和little endian称作“大尾”和“小尾”。</p>
<p>1、字符编码、内码，顺带介绍汉字编码<br />
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码，为了处理汉字，程序员设计了用于简体中文的GB2312和用于繁体中文的big5。</p>
<p>GB2312(1980年)一共收录了7445个字符，包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7，低字节从A1-FE，占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。</p>
<p>GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号，它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字，同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030，对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。</p>
<p>从ASCII、GB2312、GBK到GB18030，这些编码方法是向下兼容的，即同一个字符在这些方案中总是有相同的编码，后面的标准支持更多的字符。在这些编码中，英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼，GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。</p>
<p>有的中文Windows的缺省内码还是GBK，可以通过GB18030升级包升级到GB18030。不过GB18030相对GBK增加的字符，普通人是很难用到的，通常我们还是用GBK指代中文Windows内码。</p>
<p>这里还有一些细节：</p>
<p>GB2312的原文还是区位码，从区位码到内码，需要在高字节和低字节上分别加上A0。</p>
<p>在DBCS中，GB内码的存储格式始终是big endian，即高位在前。</p>
<p>GB2312的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析：在读取DBCS字符流时，只要遇到高位为1的字节，就可以将下两个字节作为一个双字节编码，而不用管低字节的高位是什么。</p>
<p>2、Unicode、UCS和UTF<br />
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容（更准确地说，是与ISO-8859-1兼容），与GB码不兼容。例如“汉”字的Unicode编码是6C49，而GB码是BABA。</p>
<p>Unicode也是一种字符编码方法，不过它是由国际组织设计，可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set"，简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。</p>
<p>根据维基百科全书(<a href="http://zh.wikipedia.org/wiki/" target="_blank">http://zh.wikipedia.org/wiki/</a>)的记载：历史上存在两个试图独立设计Unicode的组织，即国际标准化组织（ISO）和一个软件制造商的协会（unicode.org）。ISO开发了ISO 10646项目，Unicode协会开发了Unicode项目。</p>
<p>在1991年前后，双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果，并为创立一个单一编码表而协同工作。从Unicode2.0开始，Unicode项目采用了与ISO 10646-1相同的字库和字码。</p>
<p>目前两个项目仍都存在，并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是10646-3:2003。</p>
<p>UCS规定了怎么用多个字节表示各种文字。怎样传输这些编码，是由UTF(UCS Transformation Format)规范规定的，常见的UTF规范包括UTF-8、UTF-7、UTF-16。</p>
<p>IETF的RFC2781和RFC3629以RFC的一贯风格，清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。</p>
<p>3、UCS-2、UCS-4、BMP</p>
<p>UCS有两种格式：UCS-2和UCS-4。顾名思义，UCS-2就是用两个字节编码，UCS-4就是用4个字节（实际上只用了31位，最高位必须为0）编码。下面让我们做一些简单的数学游戏：</p>
<p>UCS-2有2^16=65536个码位，UCS-4有2^31=2147483648个码位。</p>
<p>UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows)，每行包含256个cells。当然同一行的cells只是最后一个字节不同，其余都相同。</p>
<p>group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中，高两个字节为0的码位被称作BMP。</p>
<p>将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节，就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。</p>
<p>4、UTF编码</p>
<p>UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下：</p>
<p>UCS-2编码(16进制) UTF-8 字节流(二进制)<br />
0000 - 007F 0xxxxxxx<br />
0080 - 07FF 110xxxxx 10xxxxxx<br />
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx </p>
<p>例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间，所以肯定要用3字节模板了：1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是：0110 110001 001001， 用这个比特流依次代替模板中的x，得到：11100110 10110001 10001001，即E6 B1 89。</p>
<p>读者可以用记事本测试一下我们的编码是否正确。</p>
<p>UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码，UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于0x10000的UCS码，定义了一个算法。不过由于实际使用的UCS2，或者UCS4的BMP必然小于0x10000，所以就目前而言，可以认为UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案，UTF-16却要用于实际的传输，所以就不得不考虑字节序的问题。</p>
<p>5、UTF的字节序和BOM<br />
UTF-8以字节为编码单元，没有字节序的问题。UTF-16以两个字节为编码单元，在解释一个UTF-16文本前，首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E，“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”，那么这是“奎”还是“乙”？</p>
<p>Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表，而是Byte Order Mark。BOM是一个有点小聪明的想法：</p>
<p>在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符，它的编码是FEFF。而FFFE在UCS中是不存在的字符，所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前，先传输字符"ZERO WIDTH NO-BREAK SPACE"。</p>
<p>这样如果接收者收到FEFF，就表明这个字节流是Big-Endian的；如果收到FFFE，就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。</p>
<p>UTF-8不需要BOM来表明字节顺序，但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF（读者可以用我们前面介绍的编码方法验证一下）。所以如果接收者收到以EF BB BF开头的字节流，就知道这是UTF-8编码了。</p>
<p>Windows就是使用BOM来标记文本文件的编码方式的。</p>
<p>6、进一步的参考资料<br />
本文主要参考的资料是 "Short overview of ISO-IEC 10646 and Unicode" (<a href="http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html" target="_blank">http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html</a>)。</p>
<p>我还找了两篇看上去不错的资料，不过因为我开始的疑问都找到了答案，所以就没有看：</p>
<p>"Understanding Unicode A general introduction to the Unicode Standard" (<a href="http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter04a" target="_blank">http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter04a</a>)<br />
"Character set encoding basics Understanding character set encodings and legacy encodings" (<a href="http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter03" target="_blank">http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&#038;item_id=IWS-Chapter03</a>)<br />
我写过UTF-8、UCS-2、GBK相互转换的软件包，包括使用Windows API和不使用Windows API的版本。以后有时间的话，我会整理一下放到我的个人主页上(<a href="http://fmddlmyy.home4u.china.com" target="_blank">http://fmddlmyy.home4u.china.com</a>)。</p>
<p>我是想清楚所有问题后才开始写这篇文章的，原以为一会儿就能写好。没想到考虑措辞和查证细节花费了很长时间，竟然从下午1:30写到9:00。希望有读者能从中受益。</p>
]]></content:encoded>
			<wfw:commentRss>https://falconia.org/blog/archives/1/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
