Convertendo UnicodeString em AnsiString

Antigamente, eu tinha uma função que convertia umWideString para umAnsiString da página de código especificada:

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;

E tudo funcionou. Passei a função aunicode (ou seja, dados codificados em UTF-16) e os converteu em umAnsiString, com o entendimento de que os bytes noAnsiString caracteres representados da página de códigos especificada.

Por exemplo:

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

retornaria oWindows-1252 sequência codificada:

The qùíçk brown fôx jumped ovêr the lázÿ dog

Nota: Obviamente, as informações foram perdidas durante a conversão do conjunto completo de caracteres Unicode para os limites limitados da página de código do Windows-1252:

Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ (antes)The qùíçk brown fôx jumped ovêr the lázÿ dog (depois de)

Mas o WindowsWideChartoMultiByte faz um bom trabalho de mapeamento da melhor forma; como foi projetado para fazer.

Agora os tempos posteriores

Agora estamos nos tempos posteriores.WideString agora é uma pária, comUnicodeString sendo a bondade. É uma mudança inconseqüente; como a função Windows precisava apenas de umponteiro para uma série deWideChar de qualquer maneira (que umUnicodeString também é). Então, alteramos a declaração para usarUnicodeString em vez de:

funtion WideStringToString(const Source: UnicodeString; CodePage: UINT): AnsiString;
begin
   ...
end;

Agora chegamos ao valor de retorno. eu tenho umAnsiString que contém os bytes:

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

Nos velhos tempos, tudo bem. Acompanhei qual página de código oAnsiString realmente contido; Eu preciseilembrar que o retornadoAnsiString não foi codificado usando a localidade do computador (por exemplo, Windows 1258), mas é codificado usando outra página de código (aCodePage página de código).

Mas no Delphi XE6 umAnsiString também secretamente contém a página de códigos:

codePage: 1258comprimento: 44valor: The qùíçk brown fôx jumped ovêr the lázÿ dog

Esta página de código está errada. Delphi está especificando a página de código do meu computador, em vez da página de código que a string é. Tecnicamente, isso não é um problema, eu sempre entendi que oAnsiString estava em uma página de código específica, eu só precisava transmitir essas informações.

Então, quando eu quis decodificar a string, tive que passar a página de código com ela:

s := TUnicodeHeper.StringToWideString(s, 1252);

com

function StringToWideString(s: AnsiString; CodePage: UINT): UnicodeString;
begin
   ...
   MultiByteToWideChar(...);
   ...
end;
Então uma pessoa estraga tudo

O problema era que antigamente eu declarava um tipo chamadoUtf8String:

type
   Utf8String = type AnsiString;

Porque era comum o suficiente ter:

function TUnicodeHelper.WideStringToUtf8(const s: UnicodeString): Utf8String;
begin
   Result := WideStringToString(s, CP_UTF8);
end;

e o inverso:

function TUnicodeHelper.Utf8ToWideString(const s: Utf8String): UnicodeString;
begin
   Result := StringToWideString(s, CP_UTF8);
end;

Agora no XE6 eu tenho uma função queleva a Utf8String. Se algum código existente em algum lugar estivesse usando um código UTF-8AnsiStringe tente convertê-lo em UnicodeString usandoUtf8ToWideString falharia:

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

Ou pior, é a amplitude do código existente que faz:

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

A string retornada ficará totalmente desconfigurada:

a função retornaAnsiString(1252) (AnsiString marcado como codificado usando a página de código atual)o resultado do retorno está sendo armazenado em umAnsiString(65001) corda (Utf8String)Delphi converte a string codificada em UTF-8 em UTF-8 como se fosse 1252.Como avançar

Idealmente meuUnicodeStringToString(string, codePage) função (que retorna umAnsiString) pode definir oCodePage dentro da string para corresponder à página de código real usando algo comoSetCodePage:

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;

Exceto que mexer manualmente com a estrutura interna de umAnsiString é terrivelmente perigoso.

Então, que tal voltarRawByteString?

Já foi dito, repetidamente, por muitas pessoas que não sou eu queRawByteString é para ser odestinatário universal; não era para ser como um parâmetro de retorno:

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;

Isso tem a virtude de poder usar os recursos suportados e documentadosSetCodePage.

Mas se vamos cruzar uma linha e começar a retornarRawByteString, certamente o Delphi já tem uma função que pode converter umUnicodeString para umRawByteString string e vice-versa:

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;

Mas o que é isso?

Ou o que mais devo fazer?

Era um conjunto de antecedentes prolongado para uma pergunta trivial. oreal A questão é, é claro, o que devo fazer em vez disso? Existe muito código por aí que depende daUnicodeStringToString e o contrário.

tl; dr:

Eu posso converter umUnicodeString para UTF fazendo:

Utf8Encode('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');

e eu posso converter umUnicodeString para a página de código atual usando:

AnsiString('Ŧĥε qùíçķ ƀřǭŵņ fôx ǰűmpεď ōvêŗ ţħě łáƶÿ ďơǥ');

Mas como faço para converter umUnicodeString para uma página de código arbitrária (não especificada)?

Meu sentimento é que, já que tudo é realmente umAnsiString:

Utf8String = AnsiString(65001);
RawByteString = AnsiString(65535);

eu deveria morder a bala, abrir oAnsiString estrutura e coloque a página de código correta nela:

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;

Em seguida, o restante da VCL ficará alinhado.

questionAnswers(3)

yourAnswerToTheQuestion