(2010-12-09)
UNICODE的添加真让人头疼(或许本来就该只有UNICODE,没有什么char只说,看来美国人对于扩展问题也没有想的很到位),问题随之而来一大堆。
此一篇讨论使用CStdioFile类或CFile类在读写.txt文件时,UNICODE遇到的麻烦。
新建一个MFC项目(或其他的也可),并在新建向导中选择使用UNICODE库(都VS2010+Win7年代了,咱也彻底使用UNICODE一把),完成向导。
不过,先来讨论一下关于几个诡异的标识符: LPSTR、LPTSTR、LPWSTR; LPCSTR、LPCTSTR、LPCWSTR。下面是一些定义:
可以看出,所有加"C"的标识符,均表示常量;而加了"T"的标识符只是一个马甲,取决于UNICODE与非UNICODE是否被定义,而分别代表宽字节与单字节。我们会发现加"T"的标识符会自动的支持UNICODE或非UNICODE。"C"和"T"可以被同时添加到一个标识符中,比如:LPCTSTR。也有不带"L"的标识符:PCTSTR,在MSDN中,PCTSTR和LPCTSTR的定义完全一样,其它不带"L"的标识符也类似。
需要特别注意的是:LPSTR/LPCSTR是明确的指明了变量是单字节,LPWSTR/LPCWSTR明确的指明了变量是宽字节。而LPTSTR/LPCTSTR要取决于工程内是否定义了UNICODE,才能知道确切的是单字节还是宽字节。上面新建的工程在一开始就定义了UNICODE,所以在该工程下面的所有代码,若使用LPTSTR/LPCTSTR定义了变量,则均是指的宽字节形式:LPWSTR/LPCWSTR,所以此时LPTSTR和LPWSTR可以互换而不会出现问题。
除了上述的一些诡异的标识符,还有一些MFC内部的函数返回值是诸如:"LPTSTR"此类的。比如CString::GetBuffer()就返回一个LPTSTR的值,此时返回的到底是单字节还是宽字节,取决于工程中是否定义了UNICODE,若定义了返回值其实就是LPWSTR,若没有定义返回值其实就是LPSTR。所以上面新建的工程,由于定义了UNICODE其返回值其实是一个LPWSTR。还有一些API函数需要一个LPTSTR的实参等等,这些实参具体取决于UNICODE是否定义,而确定是单字节或宽字节。
为什么是宽字节而不是双字节?因为这要按照UNICODE的编码格式。UNICODE编码格式(Unicode Transformation Format,简称UTF)分为3种:UTF-8、UTF-16、UTF-32。在Win7中,每个UNICODE字符都使用UTF-16编码,所以在Win7中UNICODE是双字节的。UTF-8是将一些字符编码为1个字节,一些字符编码为2个字节,一些字符编码为3个字节,一些字符编码为4个字节。UTF-16是将每个字符编码为2个字节。UTF-32是将每个字符编码为4个字节。
当你保存一个文件至.txt时,可以选择:UTF-8、UNICODE、ANSI等不同的编码格式。ANSI(GB2312、BIG5、JIS等)是什么?他是根据不同国家或地区的文字进行编码的另一种方式。很像UTF-8,但又不是。ANSI在表示ASCII字符时占用1个字节,在表示中文的1个字时占用2个字节。保存到.txt时,ANSI格式的.txt文件没有前缀;UTF-8包括3个前缀:EF BB BF;UNICODE包括2个前缀:FF FE。所以在读取这些不同编码类型的文件时应该适当的去掉前缀,再做进一步处理。去掉前缀后,根据不同的编码类型保存的二进制文件也不同。UNICODE每个字符不论是ASCII还是中文,都是由2个字节表示。UTF-8基本和ANSI类似。
好,下面是用CStdioFile类的Open()函数打开一个ASNI编码格式的.txt文件(不用再对其做前缀处理了),Open()函数需要一个LPCTSTR的文件名变量,由于已经定义了UNICODE所以这个变量其实是LPCWSTR。然后使用CStdioFile类的ReadString(CString& rString)函数读取第一句话。接着我们使用MessageBox()把该rString显示出来,编译时没有问题,但是在运行时会发现其实MessageBox()显示出的那句话是乱码!嗯,我用MultiByteToWideChar()函数将CString类型的rSring转换为宽字节,会出现编译错:“error C2664: “MultiByteToWideChar”: 不能将参数 3 从“CString”转换为“LPCSTR ”。原来rString已经是一个LPWSTR,所以MultiByteToWideChar()函数无法将其转化为LPCSTR,即由一个宽字节转换为单字节。为什么rString是一个LPWSTR?就是因为我们已经把程序定义为了UNICODE的!我找了多种方法将CString转换为LPCSTR都不能让MessageBox()正常显示,这其实相当于把LPWSTR转换为LPCSTR。现在是修改程序的编译字符集为:“未设置”,MultiByteToWideChar()函数正常执行,MessageBox()函数显示的文字也不再是乱码。
这里面的问题是,一是不能把获取的ANSI编码格式的字符集用UNICODE的方式做显示;再就是由于程序已经定义了UNICODE,若使用类似的把一些单字节变量变成宽字节的函数就会出现问题,因为该变量已经是宽字节的(在本程序中是UNICODE格式的)。
由于有多种编码共同存在(比如:ANSI、GB2312、UTF-8、UNICODE),在处理程序中止选择UNICODE是不明智更甚是错误的。比较好的处理方式是先获取其单字节字符集,然后使用MultiByteToWideChar()函数来转换,该函数的第一个参数就是确定编码字符集,比如CP_ACP是ANSI编码格式等。
在嵌入式平台Windows CE开发中,由于其已经全部是UNICODE方式,即所有编码都是2个字节(UTF-16),但是存在于Windows CE中的文件可能还是ANSI的(这样才能和PC中的文件兼容),所以在读写Windows CE中的文件时,就必须使用单字节先获取文档内容,然后再使用MultiByteToWideChar()函数指定其编码格式,来处理或显示ANSI(或其他类似字符集)字符集。
参考资料:
[1]. 详细的API函数说明,请参考MSDN: http://msdn.microsoft.com/en-us/library/default.aspx 。
[2]. Charles Petzold,《Windws程序设计》。
[3]. Jeffrey Richter,《Windows核心编程》。