在过去,我有一个函数可以将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
但是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
代码页)进行编码。但是在Delphi XE6中,
AnsiString
也 secret 包含了代码页: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
)如何前进
理想情况下,我的
UnicodeStringToString(string, codePage)
函数(返回AnsiString
)可以使用类似 CodePage
的方式将字符串内的SetCodePage
设置为与实际代码页匹配: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; dr:
我可以通过执行以下操作将
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将排成一行。
最佳答案
在这种特殊情况下,使用RawByteString
是合适的解决方案:
function WideStringToString(const Source: UnicodeString; CodePage: UINT): RawByteString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(Result, CodePage, False);
end;
end;
这样,
RawByteString
保留了代码页,并将RawByteString
分配给任何其他字符串类型,无论是AnsiString
还是UTF8String
或任何其他类型,都将允许RTL自动将RawByteString
数据从其当前代码页转换为目标字符串的代码页(包括转换为UnicodeString
)。如果绝对必须返回
AnsiString
(我不建议这样做),则仍然可以通过类型转换使用SetCodePage()
:function WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
var
strLen: Integer;
begin
strLen := LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), nil, 0, nil, nil));
if strLen > 0 then
begin
SetLength(Result, strLen);
LocaleCharsFromUnicode(CodePage, 0, PWideChar(Source), Length(Source), PAnsiChar(Result), strLen, nil, nil));
SetCodePage(PRawByteString(@Result)^, CodePage, False);
end;
end;
反之则容易得多,只需使用已经存储在
(Ansi|RawByte)String
中的代码页(只需确保这些代码页始终准确)即可,因为RTL已经知道如何为您检索和使用该代码页:function StringToWideString(const Source: AnsiString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
function StringToWideString(const Source: RawByteString): UnicodeString;
begin
Result := UnicodeString(Source);
end;
话虽这么说,我建议完全放弃帮助器函数,而只使用类型化的字符串。让RTL为您处理转换:
type
Win1252String = type AnsiString(1252);
var
s: UnicodeString;
a: Win1252String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
a := Win1252String(s);
s := UnicodeString(a);
end;
var
s: UnicodeString;
u: UTF8String;
begin
s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
u := UTF8String(s);
s := UnicodeString(u);
end;