在过去,我有一个功能可以将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)进行编码,而是使用另一个代码页({{1}进行编码代码页)。
但在Delphi XE6中,CodePage
也秘密包含代码页:
AnsiString
此代码页错误。 Delphi指定了我的计算机的代码页,而不是字符串所在的代码页。从技术上讲,这不是问题,我总是理解The qùíçk brown fôx jumped ovêr the lázÿ dog
在特定的代码页中,我只需要确保传递这些信息。
因此,当我想解码字符串时,我不得不随身携带代码页:
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;
现在在XE6中我有一个采用 a function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
Result := StringToWideString(s, CP_UTF8);
end;
的功能。如果某处的某些现有代码采用UTF-8编码Utf8String
,并尝试使用AnsiString
将其转换为UnicodeString,则会失败:
Utf8ToWideString
或者更糟糕的是,现有代码的广度是:
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
以匹配使用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
,那么Delphi肯定有一个可以将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
转换为UTF:
UnicodeString
我可以使用:
将Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
转换为当前代码页
UnicodeString
但是如何将AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');
转换为任意(未指定)的代码页呢?
我的感觉是因为一切都是UnicodeString
:
AnsiString
我应该咬紧牙关,打开Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);
结构,并将正确的代码页戳入其中:
AnsiString
然后VCL的其余部分将排成一行。
答案 0 :(得分: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;
答案 1 :(得分:5)
我认为返回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
。当然,你需要用一小撮盐来its documentation: LocaleCharsFromUnicode是WideCharToMultiByte函数的包装器。令人惊讶的是,自从LocaleCharsFromUnicode
确实只存在于跨平台时,该文本才被写入。
但是,我想知道您是否在尝试在程序中的AnsiString
变量中保留ANSI编码文本时出错。通常,您将尽可能晚地(在互操作边界处)编码为ANSI,并尽可能早地解码。
如果你只是必须这样做,那么也许有一个更好的解决方案可以完全避免可怕的AnsiString
。不是将文本存储在AnsiString
中,而是将其存储在TBytes
中。您已经拥有跟踪编码的数据结构,因此为什么不保留它们。将包含代码页的记录和AnsiString
替换为包含代码页和TBytes
的记录。然后你就不用担心在背后记录你的文字了。您的代码将可以在移动编译器上使用。
答案 2 :(得分:3)
通过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;
注意:任何已发布到公共领域的代码。无需归属。