|
下面两个问题一直困扰着我这个处于第1层的菜鸟:1、为什么卡巴斯基KIS2009简体中文版安装在繁体XP下能正常显示简体中文,而瑞星、金山等则是乱码?2、为什么简体的MS Word或Firefox能显示繁体中文(特指Big5编码)的内容?处于1层以上的程序员(见《程序员》6月杂志周伟明的《程序员的十层楼》)肯定已经笑出来了,请略过剩下的内容。。。
这么说吧,如果想实现一个可以在简体中文系统中显示繁体中文的“记事本”,C++ Builder2009中如何实现?很多人(包括我)在没有了解各种字符编码以前,想当然地觉得既然支持Unicode了,那么直接使用TMemo的LoadFromFile方法直接load一个繁体中文的文档就能显示了。因为支持Unicode了嘛,Unicode就是在任何系统都能显示正常。好像很对,先试一下,初探嘛。
一、不变的“简体中文”版 既然支持Unicode,搞一个Form,放一个Button在上面,

Caption先不要去动,用程序去修改:
Form1->Caption = "简体中文"; Button1->Caption = "汉字";


简体XP下显示正常,可繁体XP下就显示乱码了。不是说支持Unicode了吗?跟踪调试一下:

简体XP下,“汉字”的“汉”编码是0x6C49,而繁体XP下,“汉”的编码变成了0x7296。我们知道“汉”在Uunicode编码中是0x6C49,说明简体XP下是正确的,而繁体XP下就不正确了。为什么会有这样的情况发生?把断点设置在“Button1->Caption = "汉字";”这一句,再用F7一直跟踪。原来是CB2009在把“汉字”赋值给Button1->Caption前,先进行了Ansi到Unicode的转换,恰恰问题就在CB2009使用的这个函数:InternalUStrFromPCharLen(Dest, Source, Length, DefaultSystemCodePage);函数在实现过程中获取了系统默认的CodePage(http://www.cppblog.com/shenhuafeng/archive/2007/04/05/21336.html),而简体XP和繁体XP的CodePage不一样(一个为936,一个为950)导致在转换到UNICODE的时候结果不一致,也就导致到繁体XP下显示为乱码。如何解决呢?在CB2009的帮助“Unicode in RAD Studio”一章中的“Issues(问题)”节中提到:运用“U”这个标量(一个宏,与VC++中的“L”类似)将ANSI字符常量强制识别为Unicode。这个过程是在编译时就已经完成,编译的时候是在简体XP下,所以程序运行时内存中存储的“汉字”Unicode是正确的。为了证实,将代码变为Button1->Caption = U"汉字";,再用F7跟踪。结果是程序一运行到这,就马上用UnicodeSetLength(var dst: UnicodeString; len: Integer);(注意dst的数据类型)来初始化一个UnicodeString类,等着给TButton赋值(TControl.SetText)了。

现在终于明白,不变的“简体中文”其实是不变的Unicode编码,已经不是我们的GB了。那么CB2009中的UnicodeString默认的CodePage是啥?调用UnicodeString.CodePage()就知道了——1200。
二、正确显示“繁体中文”
简体XP下显示繁体好像都很容易:用IE、Firefox浏览繁体网站,用MS Word打开繁体内容doc文档等。如何用CB2009也实现相应功能?先试试用Memo控件来Load一个繁体文本看看:

结果肯定是乱码,繁体XP下运行这个程序是能正常显示的。Memo控件中的每一行其实都是UnicodeString(属性Lines是TStrings类的对象),而繁体内容的TXT文本按ANSI保存,在Memo载入文件的时候做了一个ANSI到Unicode的转换。有了之前的跟踪结果,可以想象CB2009是获取了系统的默认CodePage(936)而导致繁体不能正确转换为UTF-16。那么我们让CB2009重新进行CodePage950的转换就应该可以正确显示了。
有个函数在前面跟踪源码的时候出现过——MultiByteToWideChar,看名字很容易理解它的作用是把多字节转为宽字符,CB2009应该是利用了这个函数将ANSI进行了转换,当然CB2009是用的简体系统默认的CodePage。转换应该可逆,那么应该有WideCharToMultiByte。实现它看看:
1
UnicodeString __fastcall BIG5ToUnicode(UnicodeString usString)
2
{
3
if
(GetOEMCP()
==
950
)
4
{
5
//
如果为繁体系统,不用转换
6
return
usString;
7
}
8
//
预分配空间
9
int
length
=
usString.Length()
*
2
;
10
char
*
chBuffer
=
new
char
[length
+
1
];
11
//
按系统默认的codepage转回去
12
int
iReturn
=
WideCharToMultiByte(GetOEMCP(),
0
, usString.w_str(),
-
1
, chBuffer, length
+
1
, NULL, NULL);
13
wchar_t
*
wcBuffer
=
new
wchar_t[iReturn
+
1
];
14
//
按Big5编码转换回来
15
iReturn
=
MultiByteToWideChar(
950
,
0
, chBuffer,
-
1
, wcBuffer, iReturn
+
1
);
16
usString
=
UnicodeString(wcBuffer);
17
delete chBuffer;
18
delete wcBuffer;
19
return
usString;
20
}
“载入文件”的Click事件实现如下:
1
void
__fastcall TForm1::btn1Click(TObject
*
Sender)
2
{
3
TStringList
*
slBuf
=
new
TStringList();
4
slBuf
->
LoadFromFile(
"
d://eula.txt
"
);
5
int
iCount
=
slBuf
->
Count;
6
for
(
int
i
=
0
; i
<
iCount; i
++
)
7
{
8
mmo1
->
Lines
->
Add(BIG5ToUnicode(slBuf
->
Strings[i]));
9
Application
->
ProcessMessages();
10
}
11
}
运行一下看看:

好像自己的两个问题有了答案,但是总觉得第二个问题的方法效率低下,毕竟又转了一道。肯定还有更好的方法,当然,在没有找到好的办法前,我们这些菜鸟用用这种方法也是可以的。毕竟成长的过程是痛苦的。
此文章来自Tuuzed(土仔)作者
|