首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >将UnicodeString转换为AnsiString

将UnicodeString转换为AnsiString
EN

Stack Overflow用户
提问于 2014-11-12 17:03:09
回答 3查看 42.9K关注 0票数 27

在过去,我有一个函数可以将WideString转换为指定代码页的AnsiString

代码语言:javascript
运行
复制
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中的字节表示指定代码页中的字符。

例如:

代码语言:javascript
运行
复制
TUnicodeHelper.WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 1252);

将返回Windows-1252编码的字符串:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
   ...
end;

现在我们得出返回值。我有一个包含字节的AnsiString

代码语言:javascript
运行
复制
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
  • 值: The qùíçk brown fôx jumped ovêr the lázÿ dog

这一页代码是错误的。Delphi指定的是我的计算机的代码页,而不是字符串的代码页。从技术上讲,这不是一个问题,我一直都知道AnsiString在一个特定的代码页面中,我只是需要确保传递这些信息。

因此,当我想解码字符串时,我必须将代码页传递给它:

代码语言:javascript
运行
复制
s := TUnicodeHeper.StringToWideString(s, 1252);

使用

代码语言:javascript
运行
复制
function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
   ...
   MultiByteToWideChar(...);
   ...
end;

然后一个人把一切都搞砸了

问题是,在过去,我声明了一个名为Utf8String的类型。

代码语言:javascript
运行
复制
type
   Utf8String = type AnsiString;

因为它很普遍,可以有:

代码语言:javascript
运行
复制
function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
   Result := WideStringToString(s, CP_UTF8);
end;

相反的是:

代码语言:javascript
运行
复制
function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
   Result := StringToWideString(s, CP_UTF8);
end;

现在,在XE6中,我有一个函数,作为Utf8String。如果某些现有代码采用UTF-8编码的AnsiString,并尝试使用Utf8ToWideString将其转换为UnicodeString,则会失败:

代码语言:javascript
运行
复制
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

或者更糟的是,现有代码的广度:

代码语言:javascript
运行
复制
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的内容。

代码语言:javascript
运行
复制
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应该是一个通用的接收者,而不是作为一个返回参数:

代码语言:javascript
运行
复制
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字符串,反之亦然:

代码语言:javascript
运行
复制
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:

代码语言:javascript
运行
复制
Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');

我可以通过以下方法将UnicodeString转换为当前代码页:

代码语言:javascript
运行
复制
AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');

但是如何将UnicodeString转换为任意(未指定的)代码页?

我的感觉是因为一切都是AnsiString

代码语言:javascript
运行
复制
Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);

我应该咬紧牙关,打开AnsiString结构,并将正确的代码页插入其中:

代码语言:javascript
运行
复制
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的其余部分就会排好队。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-11-12 17:37:16

在这种情况下,使用RawByteString是一个合适的解决方案:

代码语言:javascript
运行
复制
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()

代码语言:javascript
运行
复制
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已经知道如何为您检索和使用代码页:

代码语言:javascript
运行
复制
function StringToWideString(const Source: AnsiString): UnicodeString;
begin
  Result := UnicodeString(Source);
end;

代码语言:javascript
运行
复制
function StringToWideString(const Source: RawByteString): UnicodeString;
begin
  Result := UnicodeString(Source);
end;

尽管如此,我建议完全放弃助手函数,而只使用键入的字符串。让RTL为您处理转换:

代码语言:javascript
运行
复制
type
  Win1252String = type AnsiString(1252);

var
  s: UnicodeString;
  a: Win1252String;
begin
  s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
  a := Win1252String(s);
  s := UnicodeString(a);
end;

代码语言:javascript
运行
复制
var
  s: UnicodeString;
  u: UTF8String;
begin
  s := 'Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ';
  u := UTF8String(s);
  s := UnicodeString(u);
end;
票数 19
EN

Stack Overflow用户

发布于 2014-11-12 17:24:32

我认为返回一个RawByteString可能是最好的选择。您可以像您所描述的那样使用AnsiString来完成它,但是RawByteString更好地捕捉了意图。在这种情况下,RawByteString在道德上被算作正式的Embarcadero建议意义上的参数。它只是一个输出,而不是一个输入。真正的关键是不要将它用作变量。

您可以这样编码:

代码语言:javascript
运行
复制
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;

然后

代码语言:javascript
运行
复制
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的记录。这样你就不会害怕有什么东西会在你背后重新记录你的文字了。您的代码将准备好在移动编译器上使用。

票数 6
EN

Stack Overflow用户

发布于 2014-11-25 20:52:24

通过System.pas,我找到了一个内置函数SetAnsiString,它可以做我想做的事情:

代码语言:javascript
运行
复制
procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);

同样重要的是,对于我来说,这个函数确实将CodePage推到了内部StrRec结构中:

代码语言:javascript
运行
复制
PStrRec(PByte(Dest) - SizeOf(StrRec)).codePage := CodePage;

这让我可以写这样的东西:

代码语言:javascript
运行
复制
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;

所以当我打电话:

代码语言:javascript
运行
复制
actual := WideStringToString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ', 850);

我得到了最终的AnsiString

代码语言:javascript
运行
复制
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成员中。

另一种

代码语言:javascript
运行
复制
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;

注意事项:发布到公共域中的任何代码。不需要归属。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/26892449

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档