在过去,我有一个函数可以将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



但是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也 secret 包含了代码页:
  • 代码页: 1258
  • 长度: 44
  • 值: 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)
  • 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, CodePage, 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; 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将排成一行。

    最佳答案

    在这种特殊情况下,使用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;
    

    09-25 19:51