从零开始,学习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  
 2 #include 
 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部分以及其他相关的,在下一篇再说吧。有什么疑问问题,欢迎提出来。

hzk16.fnt文件下载

参考

  1. 谈谈Windows程序中的字符编码
  2. MSDN GetCPInfoEx function
  3. MSDN Code Page Identifiers
  4. MSDN GetLocaleInfoEx function
  5. TranslateCharsetInfo()函数调用错误
  6. 编码大全 CodePage CharSet 中英文显示名称
  7. TranslateCharsetInfo function
  8. 关于编码问答
  9. 关于unicode,mbcs,utf8,charset,encoding等相关概念的说明
  10. 输入法到编辑器字符编码识别
  11. 字符,字节和编码
  12. 查看本地windows的字符集方法
  13. 字符集和字符编码[订正]
  14. Microsoft Locale ID Values
  15. strlen, strlen_l, wcslen, wcslen_l, _mbslen, _mbslen_l, _mbstrlen, _mbstrlen_l
  16. windows的CP_ACP代码页与CP_OEMCP代码页区别
  17. 字符集编码问题
  18. 中文化和国际化问题权威解析之一:字符编码发展历程
  19. 你见过支持四字节 GB18030-2005 内码文本显示的编辑器吗?
  20. Windows 874
  21. 泰文字
  22. 点阵字体和TrueType字体
  23. TrueType

你可能感兴趣的:(win32)