- 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;