在过去,我有一个函数可以将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
是善良的。这是一个无关紧要的变化;因为WideChar
函数只需要一个指向一系列UnicodeString
的指针(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
也秘密地包含了代码页:
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
,那么德尔福肯定已经有了一个函数,它可以将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的其余部分就会排好队。
发布于 2014-11-12 17:37:16
在这种情况下,使用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;
发布于 2014-11-12 17:24:32
我认为返回一个RawByteString
可能是最好的选择。您可以像您所描述的那样使用AnsiString
来完成它,但是RawByteString
更好地捕捉了意图。在这种情况下,RawByteString
在道德上被算作正式的Embarcadero建议意义上的参数。它只是一个输出,而不是一个输入。真正的关键是不要将它用作变量。
您可以这样编码:
function MBCSString(const s: UnicodeString; CodePage: Word): RawByteString;
var
enc: TEncoding;
bytes: TBytes;
begin
enc := TEncoding.GetEncoding(CodePage);
try
bytes := enc.GetBytes(s);
SetLength(Result, Length(bytes));
Move(Pointer(bytes)^, Pointer(Result)^, Length(bytes));
SetCodePage(Result, CodePage, False);
finally
enc.Free;
end;
end;
然后
var
s: AnsiString;
....
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1251);
Writeln(StringCodePage(s));
s := MBCSString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 65001);
Writeln(StringCodePage(s));
输出1252,1251,然后如您所期望的65001。
如果你愿意的话,你可以使用LocaleCharsFromUnicode
。当然,您需要带一点盐来接受其文件:LocaleCharsFromUnicode是WideCharToMultiByte函数的包装器。令人惊讶的是,自从LocaleCharsFromUnicode
出现以来,文本就被写成了跨平台。
但是,我想知道您在尝试将ANSI编码的文本保存在程序中的AnsiString
变量中时是否犯了错误。通常,您将编码到ANSI尽可能晚(在互操作边界),并同样尽早解码。
如果您只需要这样做,那么也许有一个更好的解决方案可以完全避免可怕的AnsiString
。与其将文本存储在AnsiString
中,不如将其存储在TBytes
中。您已经有了跟踪编码的数据结构,那么为什么不保持它们呢?将包含代码页和AnsiString
的记录替换为包含代码页和TBytes
的记录。这样你就不会害怕有什么东西会在你背后重新记录你的文字了。您的代码将准备好在移动编译器上使用。
发布于 2014-11-25 20:52:24
通过System.pas
,我找到了一个内置函数SetAnsiString
,它可以做我想做的事情:
procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);
同样重要的是,对于我来说,这个函数确实将CodePage推到了内部StrRec结构中:
PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;
这让我可以写这样的东西:
function WideStringToString(const s: UnicodeString; DestinationCodePage: Word): AnsiString;
var
strLen: Integer;
begin
strLen := Length(Source);
if strLen = 0 then
begin
Result := '';
Exit;
end;
//Delphi XE6 has a function to convert a unicode string to a tagged AnsiString
SetAnsiString(@Result, @Source[1], strLen, DestinationCodePage);
end;
所以当我打电话:
actual := WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 850);
我得到了最终的AnsiString
codePage: $0352 (850)
elemSize: $0001 (1)
refCnt: $00000001 (1)
length: $0000002C (44)
contents: 'The qùíçk brown fôx jumped ovêr the láZÿ dog'
一个具有适当代码页的AnsiString已经填充在秘密codePage
成员中。
另一种
class function TUnicodeHelper.ByteStringToUnicode(const Source: RawByteString; CodePage: UINT): UnicodeString;
var
wideLen: Integer;
dw: DWORD;
begin
{
See http://msdn.microsoft.com/en-us/library/dd317756.aspx
Code Page Identifiers
for a list of code pages supported in Windows.
Some common code pages are:
CP_UTF8 (65001) utf-8 "Unicode (UTF-8)"
CP_ACP (0) The system default Windows ANSI code page.
CP_OEMCP (1) The current system OEM code page.
1252 Windows-1252 "ANSI Latin 1; Western European (Windows)", this is what most of us in north america use in Windows
437 IBM437 "OEM United States", this is your "DOS fonts"
850 ibm850 "OEM Multilingual Latin 1; Western European (DOS)", the format accepted by Fincen for LCTR/STR
28591 iso-8859-1 "ISO 8859-1 Latin 1; Western European (ISO)", Windows-1252 is a super-set of iso-8859-1, adding things like euro symbol, bullet and ellipses
20127 us-ascii "US-ASCII (7-bit)"
}
if Length(Source) = 0 then
begin
Result := '';
Exit;
end;
// Determine real size of final, string in symbols
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), nil, 0);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not get wide length of UTF-16 string. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
// Allocate memory for UTF-16 string
SetLength(Result, wideLen);
// Convert source string to UTF-16 (WideString)
// wideLen := MultiByteToWideChar(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(wideStr), wideLen);
wideLen := UnicodeFromLocaleChars(CodePage, 0, PAnsiChar(Source), Length(Source), PWChar(Result), wideLen);
if wideLen = 0 then
begin
dw := GetLastError;
raise EConvertError.Create('[StringToWideString] Could not convert string to UTF-16. Error '+IntToStr(dw)+' ('+SysErrorMessage(dw)+')');
end;
end;
注意事项:发布到公共域中的任何代码。不需要归属。
https://stackoverflow.com/questions/26892449
复制相似问题