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,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
Note: Information was of course lost during the conversion from the full Unicode character set to the limited confines of the Windows-1252 code page:
Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ
(before)The qùíçk brown fôx jumped ovêr the lázÿ dog
(after)
但是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也秘密地包含了代码页:
> codePage:1258
长度:44
>值:qùíçk棕色fôx跳过了ÿ狗
此代码页错误。 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)可以将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,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的其余部分将落在一起。
解决方法
function WideStringToString(const Source: UnicodeString; CodePage: UINT): RawByteString; var strLen: Integer; begin strLen := LocaleCharsFromUnicode(CodePage,nil,nil)); if strLen > 0 then begin SetLength(Result,strLen); LocaleCharsFromUnicode(CodePage,PAnsiChar(Result),nil)); SetCodePage(Result,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,nil)); SetCodePage(PRawByteString(@Result)^,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;