Преобразование UnicodeString в AnsiString

в старые времена у меня была функция, которая преобразует 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;

и все работало. Я передал функцию a unicode string (т. е. кодированные данные 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-1252:

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

и WideChartoMultiByte делает довольно хорошую работу наиболее подходящего отображения; как он предназначен для этого.

теперь после раз

теперь мы находимся в after times. WideString теперь пария, с UnicodeString быть добродетелью. Это несущественное изменение; поскольку функция Windows нуждалась только в указатель серия WideChar в любом случае (которого UnicodeString тоже есть). Поэтому мы меняем декларацию на use 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 an AnsiString также тайно содержит кодовую страницу:

  • кодовая страница: 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 у меня есть функция, которая принимает a Utf8String. Если какой-то существующий код где-то был, возьмите кодировку UTF-8 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)
  • 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 строку и обратно 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;

но что это?

или что еще мне делать?

это был длинный набор фона для тривиального вопроса. The реальные вопрос, конечно, что я должен делать вместо этого? Существует много кода, который зависит от 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 будет соответствовать.

3 ответов


в данном конкретном случае, используя 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 держит codepage и назначение RawByteString для любого другого типа String, будь то AnsiString или UTF8String или что угодно, позволит RTL автоматически преобразовать RawByteString данные из текущей кодовой страницы в кодовую страницу строки назначения (которая включает преобразования в UnicodeString).

если вы абсолютно должны возвратить AnsiString (что я и делаю не рекомендую), вы все равно можете использовать SetCodePage() через typecast:

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;

я думаю, что возврат 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 если вы предпочитаете. Конечно, вам нужно взять документация с щепоткой соли: LocaleCharsFromUnicode-это оболочка для функции WideCharToMultiByte. Удивительно, что текст был написан с LocaleCharsFromUnicode конечно, существует только кросс-платформенный.


тем не менее, мне интересно, может быть, вы делаете ошибку, пытаясь сохранить кодированный текст ANSI в AnsiString переменных в вашей программе. Обычно вы кодируете ANSI как можно позже (на границе взаимодействия), а также декодируете как можно раньше.

если вы просто должны сделать это, то, возможно, есть лучшее решение, чтобы избежать ужасной AnsiString полностью. Вместо того чтобы хранить текст в AnsiString, магазина в TBytes. У вас уже есть структуры данных, которые отслеживают кодировку, так почему бы не сохранить их. Замените запись, содержащую кодовую страницу и AnsiString С один, содержащий кодовую страницу и TBytes. Тогда вы не будете бояться, что что-то перекодирует ваш текст за вашей спиной. И ваш код будет готов к использованию на мобильных компиляторов.


самоунижение через System.pas, Я нашел встроенную функцию SetAnsiString есть:

procedure SetAnsiString(Dest: _PAnsiStr; Source: PWideChar; Length: Integer; CodePage: Word);

также важно отметить, что эта функция тут вставьте кодовую страницу во внутреннюю структуру 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: 52 (850)
elemSize: 01 (1)
refCnt:   000001 (1)
length:   00002C (44)
contents: 'The qùíçk brown fôx jumped ovêr the láZÿ dog' 

An 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;

Примечание: любой код, выпущенный в общественное достояние. Атрибуция не требуется.