从零开始,学习windows编程(9)-- 字符编码以及国际化(1)
前言
前面写的8篇文章,基本上将VC环境下命令行模式程序的入口,以及链接库的知识进行了介绍,基本可以告一段落了。至于kernel32.lib包括后面的user32.lib等,之后介绍win32应用程序(Win32 Application)的时候会介绍到,这里就不展开了。另外,忽然想到,这里写的程序都是win32程序,至于64位的程序,具体还没有开发过,也就不涉及了,以后如果业务上使用到的时候,再具体研究之后加上。
这里新的话题就是字符编码以及国际化的问题。在写完这个话题之后,我们就将开始"Win32 Application"的旅程。
从一个例子开始
既然你在看这篇文章,那我就假定你是一个开发者,并且对于ANSI,ASCII,Unicode,UTF8这些名词都有所了解,至少是听说过这些概念。因为这个例子中,我们就会使用到这些概念。如果你对于这些概念不熟悉,那请先从我的“参考”链接中找到对应的条目进行研究,然后再开始做这个例子。
OK,那我们就可以开始了。首先,打开你的Notepad,中文系统中叫做“记事本”。下面是一个空的打开的记事本:
我们来输入一些文字。
我们现在输入了一行中文,一行英文,并且带了一些标点符号,同时有一个换行符。
将其保存起来,命名为"hello.txt",位置放在"d:\test"目录下。
到文件管理器下,我们可以看到hello.txt这个文件,大小为27个byte。
现在我们需要用一些其他的辅助工具,我这里用的是Notepad++以及它的一个叫HexEditor的插件,不喜欢使用的同学们可以用其他类似的工具代替,如WinHex或HexEdit等十六进制编辑器。用工具将这个txt文件打开,来看看这27个byte是怎么组成的。
打开文件后,点击工具栏上的“H”图标,我们来看看十六进制。
其实这里的对应是有些小问题的,对于中文的部分,dump出来的映射关系在移动光标的时候有些不准确,但是这个并不是影响很大,我们可以忽略。我们可以挑选其中几个来看看,这里的“你”对应的十六进制为“c4 e3”,也就是0xC4E3;"l"对应的十六进制为"6c",即0x6C。我们在这里看到的映射关系,就叫做字符编码(character encoding),而所有字符编码的集合,我们叫做字符集(character set--charset)。
我们在这里看到的是ANSI编码的,可以回过来看一下“保存”的那个截图,最下方的"encoding"选择的是"ANSI"。我们在保存文件的时候,还可以有其他选择。用saveas来另存一下。
可以看到,除了ANSI选项外,我们还有3个选项。
这里我们选择使用"unicode",其他两个也是unicode的概念,只不过是不同形式的表示,我们之后会进行解释,将其保存为"hello_unicode.txt",从文件管理器中查看,其大小为44个byte。同样的内容,换了不同的encoding,大小也变得不同了。来看看到底是为什么吧。
我们也将其用Notepad++打开,查看其十六进制。
这里Dump出来怎么都是“乱码”呀?不要慌张,这是因为dump使用的是ANSI编码显示,而现在我们用的是unicode编码。这里文件最前面的"ff fe"是用来判断"大小端"的,如果你学过TCP/IP或者计算机基础课程,那应该对于这个概念比较熟悉,如果不熟悉,那就搜索一下"littlen endian, big endian"来看看吧,这个对于文件是需要的,但是对于字符串等在内存中的字符表示,就不需要了,所以这里只需要了解就可以了。字符“你”的对应十六进制编码为"60 4f",而这里是小端模式,即0x4F60。要验证的话,也很简单,我博客中有篇文章中已经介绍过了,可以参考巧用WORD进行字符与Unicode字符的转换中的内容,用word来验证一下。
当你有耐心看到这里,并且明白了我上面所写的内容的话,背景介绍完毕。下面就开始真正介绍“字符编码”了。
字符集
首先,上面我们所做的都是针对简体中文环境下的字符编码,至于其他编码,我们还没有涉及到,之后会谈到。对于“你”这个字符,在ANSI中,为0xC4E3;在Unicode中,为0x4F60。这是由不同的标准所决定的,决定前面的标准为中国国家标准,包括一系列一脉相承的标准,从GB2312到GBK到GB18030。决定后面的为The Unicode Standard。为什么会出现这么多标准,这是由历史原因以及当时的机器性能决定的。
历史
刚刚前面一直提到ANSI,其实并不是那么准确的,其中我们要涉及到codepage的概念,这里我们的codepage为936,也就是简体中文,在这个环境下,我们的ANSI编码表示才是中国国家标准的那套。ANSI真正的含义是“美国国家标准协会”(American National Standards Institute)。就是这个协会,定义了字符编码的“鼻祖”--ASCII码。现在的字符编码,大多是基于ASCII码扩展而来的。
ASCII(American Standard Code for Information Interchange, 美国信息交换标准代码)是一个7位(7-bits)码,包含了128个字符的定义(128=2^7),其中有33个非打印控制字符,以及95个可打印字符(包括空格),主要是用来显示英文字母的,其对应的ISO标准为ISO646。为了避免概念混淆,IANA(Internet Assigned Numbers Authority,互联网号码分配局)使用US-ASCII来称呼该字符集。至于为什么定义7位而不是一下子8位,当时是个“锱铢必较”的时代,内存太紧俏太贵了,能省一点是一点哈,够用就行。
尽管能表示英文字母,但是还是有很多字符还是无法表示,比如德文中的öäüß等,于是不够用了,然后就出现了扩展ASCII码(Extended ASCII),也就是将ASCII码从7位扩展成了8位,这样一共就有了256个字符。EASCII码比ASCII码扩充出来的符号包括表格符号、计算符号、希腊字母和特殊的拉丁符号。这里,就开始有了区分,对应的标准并不是一个,而是一系列的8位字符集标准,在ISO标准中叫做ISO 8859,全称为ISO/IEC 8859,现在定义了15个字符集(这里有16个,应该是希伯来语一个是视觉顺序,一个是逻辑顺序,为1个,猜想,未验证)。
- ISO/IEC 8859-1 (Latin-1) - 西欧语言
- ISO/IEC 8859-2 (Latin-2) - 中欧语言
- ISO/IEC 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
- ISO/IEC 8859-4 (Latin-4) - 北欧语言
- ISO/IEC 8859-5 (Cyrillic) - 斯拉夫语言
- ISO/IEC 8859-6 (Arabic) - 阿拉伯语
- ISO/IEC 8859-7 (Greek) - 希腊语
- ISO/IEC 8859-8 (Hebrew) - 希伯来语(视觉顺序)
- ISO 8859-8-I - 希伯来语(逻辑顺序)
- ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
- ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼语支,用来代替Latin-4。
- ISO/IEC 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
- ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波罗的语族
- ISO/IEC 8859-14(Latin-8 或 Celtic)- 凯尔特语族
- ISO/IEC 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的芬兰语字母和大写法语重音字母,以及欧元(€)符号。
- ISO/IEC 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
上面的这些字符问题,基本解决了,下面就是比较麻烦的了。上面的字符,用1个byte都能够表示完整了,叫做SBCS(Single Byte Character Set)。但是咱们是象形文字啊,几千个字符,这小小256个地方怎么够用呢?于是没办法,扩1个byte吧,于是叫DBCS(Double Byte Character Set)。对于CJK语言(以中文、日文、韩文为主的东亚语言),基本上使用2个byte表示一个字符的编码方式来进行表示。具体了解的有日文、简体中文、韩文、繁体中文(至于泰文并不是很清楚,据有些同学说是3字节编码?资料没有找到很确切的,希望了解的同学指导一下)。
我们最熟悉的当然是简体中文了,所以就看这个,其余除了以前玩大宇游戏的时候,知道“大五码”(Big 5),用过南极星,就了解不深了。这里就不做介绍了。
对于简体中文编码,最开始的就是GB2312编码(GB 2312-80),是中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集·基本集》,又稱GB0,由中国国家标准总局发布,1981年5月1日实施。GB2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。
尽管覆盖了99.75%,但是还是有没覆盖的,所以之后又出现了GBK和GB-18030。这里对于编程人员的建议就是,如果需要更全的字符覆盖,就使用GB-18030,如果是嵌入式系统,需要在更小的内存中实现较多的功能,使用GB-2312即可,甚至在某些场合,可以使用其子集(一般嵌入式系统需要显示的字符是可以统计出来的,有些字符并不需要)。
这样,对于非Unicode部分的字符集,基本上要了解的都介绍了。不过,如何使用呢?我Latin-1的和GB 2312的,其同一个十六进制,对应的字符不一样、同样用“你”的编码来说,对应为0xC4E3,对应到Latin-1中,就是两个字符,Äã,两个是不能共存的。
在这种情况下,有一个新的概念出现了:
codepage(代码页)
为了使用不同的字符集,我们使用不同的codepage。不同的codepage对应不同的字符集。对于简体中文,其codepage为936;日文的codepage为932;韩文的codepage为949;繁体中文的codepage为950。我们可以参考Windows下面的Region and Language Option设置,对于“非Unicode”语言设置,选择的就是特定的codepage。
字体文件的显示
字体文件的显示,也是经过长时间的演变过来的。一开始的字体为“点阵字体”,后来出现了PostScript字体,TrueType字体(True Type Font, TTF),我们这里就看一下具体点阵字体的显示过程。一般嵌入式方案或者有些游戏中还是使用该种显示。这里我们使用GB 2312的一个点阵字体方案,叫做“hzk16.fnt”(最后部分有下载,也可google到),是提供16*16的点阵字体给用户使用。这里我写了一个比较简单的程序来进行显示:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 int printHZK16(char gbcode[3]);
6
7 int main()
8 {
9 char input[10]={0};
10 while(1)
11 {
12 printf("输入中文,一次一个字符:");
13 printf("\n");
14 scanf("%s",input);
15 printHZK16(input);
16 printf("\n");
17 }
18
19 return 0;
20 }
21
22 int printHZK16(char gbcode[3])
23 {
24 //要读出的汉字,GB2312编码
25 unsigned char incode[3];
26 unsigned char qh = 0, wh = 0;
27 unsigned long offset = 0;
28 char mat[16][2] = {0};
29 FILE *HZK = 0;
30 int i,j,k;
31
32 memcpy(incode, gbcode, 3);
33
34 //每个汉字,占2个字节,取其区位码
35 qh = incode[0] - 0xa0;//获得区码
36 wh = incode[1] - 0xa0;//获得位码
37 offset = (94*(qh-1)+(wh-1))*32; //得到偏移位置
38 //打开hzk16.fnt文件
39 if((HZK=fopen("hzk16.fnt", "rb")) == NULL)
40 {
41 printf("Can't Open hzk16\n");
42 getchar();
43 return 0;
44 }
45 //根据 offset进行定位
46 fseek(HZK, offset, SEEK_SET);
47 //读取32字节字模
48 fread(mat, 32, 1, HZK);
49 fclose(HZK);
50
51 //显示
52 for(i=0; i<16; i++)
53 {
54 for(j=0; j<2; j++)
55 {
56 for(k=0; k<8; k++)
57 {
58 if(mat[i][j] & (0x80>>k))
59 {
60 //测试为1的位则进行显示
61 printf("%c",'#');
62 }
63 else
64 {
65 printf("%c",' ');
66 }
67 }
68 }
69 printf("\n");
70 }
71 getchar();
72 return 1;
73 }
效果如下:
写得有点累了,放假还是休息休息吧,这篇就先介绍非Unicode部分了;Unicode部分以及其他相关的,在下一篇再说吧。有什么疑问问题,欢迎提出来。
参考
- 谈谈Windows程序中的字符编码
- MSDN GetCPInfoEx function
- MSDN Code Page Identifiers
- MSDN GetLocaleInfoEx function
- TranslateCharsetInfo()函数调用错误
- 编码大全 CodePage CharSet 中英文显示名称
- TranslateCharsetInfo function
- 关于编码问答
- 关于unicode,mbcs,utf8,charset,encoding等相关概念的说明
- 输入法到编辑器字符编码识别
- 字符,字节和编码
- 查看本地windows的字符集方法
- 字符集和字符编码[订正]
- Microsoft Locale ID Values
- strlen, strlen_l, wcslen, wcslen_l, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l
- windows的CP_ACP代码页与CP_OEMCP代码页区别
- 字符集编码问题
- 中文化和国际化问题权威解析之一:字符编码发展历程
- 你见过支持四字节 GB18030-2005 内码文本显示的编辑器吗?
- Windows 874
- 泰文字
- 点阵字体和TrueType字体
- TrueType