【发布时间】:2015-01-09 15:04:58
【问题描述】:
在过去,我有一个函数可以将指定代码页的 WideString 转换为 AnsiString:
function WideStringToString(const Source: WideString; CodePage: UINT): AnsiString;
...
begin
...
// Convert source UTF-16 string (WideString) to the destination using the code-page
strLen := WideCharToMultiByte(CodePage, 0,
PWideChar(Source), Length(Source), //Source
PAnsiChar(cpStr), strLen, //Destination
nil, nil);
...
end;
一切正常。我向函数传递了一个 unicode 字符串(即 UTF-16 编码数据)并将其转换为 AnsiString,并理解 AnsiString 中的字节表示来自指定代码页的字符.
例如:
TUnicodeHelper.WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
将返回Windows-1252 编码字符串:
The qùíçk brown fôx jumped ovêr the lázÿ dog
注意:在从完整的 Unicode 字符集到 Windows-1252 代码页的有限范围的转换过程中,信息当然会丢失:
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ(之前)The qùíçk brown fôx jumped ovêr the lázÿ dog(之后)
但是 Windows WideChartoMultiByte 在最佳匹配映射方面做得非常好;就像它设计的那样。
现在是以后
现在我们处于后期。 WideString 现在是贱民,UnicodeString 是善良的。这是一个无关紧要的变化;因为 Windows 函数只需要一个指向一系列WideChar 的指针(UnicodeString 也是)。所以我们将声明改为使用UnicodeString:
funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
...
end;
现在我们来看看返回值。我有一个包含字节的AnsiString:
54 68 65 20 71 F9 ED E7 The qùíç
6B 20 62 72 6F 77 6E 20 k brown
66 F4 78 20 6A 75 6D 70 fôx jump
65 64 20 6F 76 EA 72 20 ed ovêr
74 68 65 20 6C E1 7A FF the lázÿ
20 64 6F 67 dog
在过去这很好。我记录了AnsiString 实际包含的代码页;我必须记住返回的AnsiString 不是使用计算机的区域设置(例如Windows 1258)编码的,而是使用另一个代码页(CodePage 代码页)编码的。 p>
但在 Delphi XE6 中,AnsiString 也秘密包含代码页:
- 代码页: 1258
- 长度: 44
-
值:
The qùíçk brown fôx jumped ovêr the lázÿ dog
此代码页错误。 Delphi 正在指定我的计算机的代码页,而不是字符串所在的代码页。从技术上讲,这不是问题,我一直都知道AnsiString 在特定的代码页中,我只需要确保传递该信息。
所以当我想解码字符串时,我必须传递代码页:
s := TUnicodeHeper.StringToWideString(s, 1252);
与
function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
...
MultiByteToWideChar(...);
...
end;
然后一个人搞砸了一切
问题是在过去我声明了一个名为Utf8String的类型:
type
Utf8String = type AnsiString;
因为它很常见:
function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
Result := WideStringToString(s, CP_UTF8);
end;
反之:
function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
Result := StringToWideString(s, CP_UTF8);
end;
现在在 XE6 中,我有一个 采用 Utf8String 的函数。如果某处的某些现有代码采用 UTF-8 编码的 AnsiString,并尝试使用 Utf8ToWideString 将其转换为 UnicodeString,它将失败:
s: AnsiString;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
...
ws: UnicodeString;
ws := Utf8ToWideString(s); //Delphi will treat s an CP1252, and convert it to UTF8
或者更糟的是,现有代码的广度:
s: Utf8String;
s := UnicodeStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', CP_UTF8);
返回的字符串将完全被破坏:
- 函数返回
AnsiString(1252)(AnsiString标记为使用当前代码页编码) - 返回结果存储在
AnsiString(65001)字符串 (Utf8String) 中 - Delphi 将 UTF-8 编码的字符串转换为 UTF-8,就好像它是 1252。
如何前进
理想情况下,我的UnicodeStringToString(string, codePage) 函数(返回AnsiString)可以使用SetCodePage 之类的东西在字符串中设置CodePage 以匹配实际代码页:
function UnicodeStringToString(s: UnicodeString; CodePage: UINT): AnsiString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
//SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
除了手动修改 AnsiString 的内部结构非常危险。
那么返回RawByteString呢?
很多不是我的人都说过,RawByteString 应该是普遍接受者;它不应该作为返回参数:
function UnicodeStringToString(s: UnicodeString; CodePage: UINT): RawByteString;
begin
...
WideCharToMultiByte(...);
...
//Adjust the codepage contained in the AnsiString to match reality
SetCodePage(Result, CodePage, False); SetCodePage only works on RawByteString
end;
这具有能够使用受支持和记录的SetCodePage 的优点。
但是如果我们要越界,然后开始返回RawByteString,那么Delphi 肯定已经有一个函数可以将UnicodeString 转换为RawByteString 字符串,反之亦然:
function WideStringToString(const s: UnicodeString; CodePage: UINT): RawByteString;
begin
Result := SysUtils.Something(s, CodePage);
end;
function StringToWideString(const s: RawByteString; CodePage: UINT): UnicodeString;
begin
Result := SysUtils.SomethingElse(s, CodePage);
end;
但它是什么?
或者我还应该怎么做?
这是一个琐碎问题的冗长背景。 真正的问题当然是我应该怎么做呢?有很多代码依赖于UnicodeStringToString 和反之。
tl;博士:
我可以通过以下方式将 UnicodeString 转换为 UTF:
Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
我可以使用以下方法将UnicodeString 转换为当前代码页:
AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
但我如何将UnicodeString 转换为任意(未指定)代码页?
我的感觉是,既然一切都是AnsiString:
Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);
我应该咬紧牙关,打开AnsiString 结构,然后将正确的代码页插入其中:
function StringToAnsi(const s: UnicodeString; CodePage: UINT): AnsiString;
begin
LocaleCharsFromUnicode(CodePage, ..., s, ...);
...
if Length(Result) > 0 then
PStrRec(PByte(Result) - SizeOf(StrRec)).codePage := CodePage;
end;
那么 VCL 的其余部分就会排成一行。
【问题讨论】:
标签: delphi unicode internationalization delphi-xe6