以前编码问题总能让自己凑或蒙过去,最近要做一个项目服务器端用python写,客户端用c++,工程编译的字符集使用UNICODE。之间通过socket进行通信,通信过程中编码转换问题把我搞得晕头转向,逼着我将编码问题好好研究一番。

 
 

首先先谈谈VC中的编码问题,首先编码我们大致可以分为两类:文件编码和内存编码。文件编码即源代码文件的编码,gbk,UTF-8等。内存编码即源代码编译成为二进制文件的时候采用的编码。

比如,在VC 2008中我们写下如下的代码:

char* a = “a中”;

让我们分别来看一下文件编码和内存编码的区别。

用UntraEdit打开该cpp文件,进入十六进制模式:我们可以看到”a中”对应的编码为:0×61 0xD6 0xD0,0×61是ASCII码中的a,0xD6 0xD0是GBK里”中”的编码。说明,在中文windows环境下,VC创建的文件编码默认是GBK的。那么我们将文件修改为UTF-8的编码格式,重新用UE以十六进制模式进入。我们看到”a中”的编码变为了0×61 0xE4 0xB8 0xAD,0×61仍然是a的编码,而中的编码变成了,0xE4 0xB8 0xAD。

从上面的试验我们还可以知道,GBK中中文为2个字节,UTF-8是3个字节。

 

下面我们研究一下内存编码,内存编码在VC中只有3个选项:Not Set,Use Multi-Byte Character Set和Use Unicode Character Set。这个属性的修改可以在工程的属性栏中找到:


 
 

让我们继续观察上面的例子。来查看一下什么是内存编码。我们在程序中设置断点,调试程序来观察在程序运行过程中,三种工程属性下,char* a所指向的那篇内存空间的值是什么,是不是如我们所认为的那样:

工程属性

Not Set

Use Multi-Byte Character Set

Use Unicode Character Set

内存

0×61 0xd6 0xd0

0×61 0xd6 0xd0

0×61 0xd6 0xd0

 
 

如上表所示,无论工程属性如何设置,”中”在内存中都是gbk编码。这样的程序拿到非中文的操作系统中执行是会出现乱码的,那么怎么才能将其用UNICODE进行编码呢?我们需要用到wchar_t这个类型,wchar_t是一种宽字类型,宽字类型的变量使用方法如下wchar_t* a = L”a中”;即在”a中”前面加上L。

现在,调试一下可以看到,a在内存中的值为0×61 0×00 0×2d 0×4e。wchar_t类型的变量会将用两个字节存储英文变量,即在原有ASCII的基础上补上两个0.而后面的0×2d 0×4e就是”中”的编码。

这样,变量a就在多种语言平台上都可以正确显示了。那么,工程属性中的三种字符集选项有什么作用呢?

让我们引用MSDN中的原文:

Generic-text

data type name

SBCS (_UNICODE,

_MBCS not

defined)

_MBCS

defined

_UNICODE

defined

_TCHAR

char

char

wchar_t

_tfinddata_t

_finddata_t

_finddata_t

_wfinddata_t

_tfinddata64_t

__finddata64_t

__finddata64_t

__wfinddata64_t

_tfinddatai64_t

_finddatai64_t

_finddatai64_t

_wfinddatai64_t

_TINT

int

int

wint_t

_TSCHAR

signed char

signed char

wchar_t

_TUCHAR

unsigned char

unsigned char

wchar_t

_TXCHAR

char

unsigned char

wchar_t

_T or _TEXT

No effect (removed by preprocessor)

No effect (removed by preprocessor)

L (converts following character or string to its Unicode counterpart)

 
 

SBCS、_MBCS和_UNICODE是三个宏,会分别在设置了Not Set、Use Multi-Byte Character Set、Use Unicode Character Set的时候定义。为了程序的可移植性,我们通常不会直接使用wchar_t和L,而是使用TCHAR和T()。这样,在工程属性设置不同的情况下,TCHAR和T()会被解释为不同的内容。

同样,我们以前测试字符串长度的函数strlen也不要直接使用,为了可移植性,我们通常使用_tcslen或者_tcsclen。

 
 

TCHAR.H routine

_UNICODE & _MBCS not defined

_MBCS defined

_UNICODE defined

_tcslen

strlen

strlen

wcslen

_tcsclen

strlen

_mbslen

wcslen

_tcsclen_l

strlen_l

_mbslen_l

wcslen_l

 
 

那么现在的问题是,我想要把数据经过网络传输,现在是否可以直接传输wchar_t*的变量呢,答案是不行的,因为wchar_t*的字符串的英文字符表示中存在00,中文英文都用两个byte。这样既浪费空间又可能会导致网络的错误。因此我们需要先将其转换成char*的那种类型,即,英文用一个byte,中文用两个byte。怎么转换呢?要使用MultiByteToWideChar和WideCharToMultiByte来互相进行转换。转换完了之后再通过网络传输。

好了,现在数据到了Python端,想想看,现在Python端收到的是什么编码?对,中文是gbk的编码。然后我们为了在python端实现国际化,要将gbk转化成unicode。方法很简单,首先先用

s = struct.unpack(’s’,mysocket.recv(2))来接收一个中文字,然后调用s.decode(”gbk”)来将编码转化成为unicode编码。这样,编码问题就完全解决了~

 

最后,我们可以通过python来观察一下中的各种编码数据:


可以很明显的看出gbk、utf-8、unicode中英文1个byte,且都和ASCII的编码方式一一致,gbk和unicode中中文2个byte,utf-8中中文3个byte。Utf-16不在本文进行讨论。

到此,已经基本讨论完了常用的编码问题。记得以前玩台湾游戏是乱码,估计就是没有国际化转化成unicode的原因。如果用unicode,我们就能够看到正确的繁体文字了。需要注意的是UNICODE编码仅仅在程序执行的时候有用到,并不会作为数据交换的时候的数据传输的文字编码或者作为文件的文字编码。