将UnicodeString转换为AnsiString

时间:2014-11-12 17:03:09

标签: delphi unicode internationalization delphi-xe6

在过去,我有一个功能可以将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函数只需要指针到一系列WideCharUnicodeString也是如此)。因此,我们将声明更改为使用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也秘密包含代码页:

  • codePage: 1258
  • 长度 44
  • 值: 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)
  • Delphi将UTF-8编码的字符串转换为UTF-8,就像它是1252一样。

如何前进

理想情况下,我的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; 和反向。

TL; DR:

我可以通过执行以下操作将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的其余部分将排成一行。

3 个答案:

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

注意:任何已发布到公共领域的代码。无需归属。

相关问题