Как сохранить строку в потоке в Delphi 7 и восстановить в мобильном приложении в XE6?

Я разрабатываю сервер и мобильный клиент, которые обмениваются данными по HTTP. Сервер написан на Delphi 7 (потому что он должен быть совместим со старым кодом), клиент - мобильное приложение, написанное на XE6. Сервер отправляет клиенту поток данных, содержащий строки. Проблема связана с кодировкой.

На сервере я пытаюсь передать строки в UTF8:

//Writes string to stream
procedure TStreamWrap.WriteString(Value: string);
var
  BytesCount: Longint;
  UTF8: string;
begin
  UTF8 := AnsiToUtf8(Value);
  BytesCount := Length(UTF8);

  WriteLongint(BytesCount); //It writes Longint to FStream: TStream

  if BytesCount > 0 then
    FStream.WriteBuffer(UTF8[1], BytesCount);
end;

Как написано в Delphi7, Value - это однобайтовая строка.

На клиенте я читаю строку в UTF8 и кодирую ее в Unicode

//Reads string from current position of stream
function TStreamWrap.ReadString: string;
var
  BytesCount: Longint;
  UTF8: String;
begin
  BytesCount := ReadLongint;
  if BytesCount = 0 then
    Result := ''
  else
  begin
    SetLength(UTF8, BytesCount);

    FStream.Read(Pointer(UTF8)^, BytesCount);

    Result := UTF8ToUnicodeString(UTF8);
  end;
end;

Но это не работает, когда я показываю строку с ShowMessage, буквы неправильные. Итак, как сохранить строку в Delphi 7 и восстановить ее в XE6 в мобильном приложении? Следует ли мне добавлять BOM в начало данных, представляющих строку?


person tikskit    schedule 12.05.2014    source источник
comment
AFAIK AnsiToUTF8 возвращает RawByteString, но вы заставляете неявное преобразование обратно в string   -  person Sir Rufo    schedule 12.05.2014
comment
@Sir Rufo На самом деле он возвращает UTF8String. Хм, в справке по Delphi 7 написано: UTF8String представляет собой строку в кодировке UTF-8. тип UTF8String = тип строка; UTF8String - это тип строк, закодированных с использованием UTF-8. UTF-8 - это эффективное кодирование символьных строк Unicode, которое учитывает тот факт, что большая часть текстовых сообщений осуществляется в ASCII, и оптимизирует кодирование этих символов. Я изменил UTF8: string на UTF8: UTF8String; на сервере, но это не помогло /   -  person tikskit    schedule 12.05.2014


Ответы (2)


Чтобы прочитать строку в кодировке UTF8 в мобильном приложении, вы используете массив байтов и класс TEncoding. Нравится:

function TStreamWrap.ReadString: string;
var
  ByteCount: Longint;
  Bytes: TBytes;
begin
  ByteCount := ReadLongint;
  if ByteCount = 0 then
  begin
    Result := '';
    exit;
  end;

  SetLength(Bytes, ByteCount);
  FStream.Read(Pointer(Bytes)^, ByteCount);
  Result := TEncoding.UTF8.GetString(Bytes);
end;

Этот код делает то, что вам нужно в XE6, но, конечно, этот код не будет компилироваться в Delphi 7, потому что он использует TEncoding. Более того, ваша реализация TStreamWrap.WriteString делает то, что вы хотите в Delphi 7, но не работает в XE6.

Теперь похоже, что вы используете одну и ту же базу кода для версий Delphi 7 и Delphi XE6. Это означает, что вам может потребоваться некоторая условная компиляция для обработки текста, который различается в этих версиях.

Лично я бы сделал это, следуя примеру TEncoding. Вам нужна функция, которая преобразует собственный Delphi string в массив байтов в кодировке UTF-8, и соответствующая функция в обратном направлении.

Итак, давайте рассмотрим функцию преобразования строки в байты. Я не могу вспомнить, есть ли у Delphi 7 TBytes тип. Я подозреваю, что нет. Итак, давайте определим это:

{$IFNDEF UNICODE} // definitely use a better conditional than this in real code
type
  TBytes = array of Byte;
{$ENDIF}

Затем мы можем определить нашу функцию:

function StringToUTF8Bytes(const s: string): TBytes;
{$IFDEF UNICODE}
begin
  Result := TEncoding.UTF8.GetBytes(s);
end;
{$ELSE}
var
  UTF8: UTF8String;
begin
  UTF8 := AnsiToUtf8(s);
  SetLength(Result, Length(UTF8));
  Move(Pointer(UTF8)^, Pointer(Result)^, Length(Result));
end;
{$ENDIF}

Функция в обратном направлении должна быть для вас тривиальной.

Когда у вас есть различия в обработке кодировки текста между двумя инкапсулированными версиями Delphi, вы можете написать условно свободный код в остальной части вашей программы. Например, вы могли бы закодировать WriteString следующим образом:

procedure TStreamWrap.WriteString(const Value: string);
var
  UTF8: TBytes;
  ByteCount: Longint;
begin
  UTF8 := StringToUTF8Bytes(Value);
  ByteCount := Length(UTF8);
  WriteLongint(ByteCount);
  if ByteCount > 0 then
    FStream.WriteBuffer(Pointer(UTF8)^, ByteCount);
end;
person David Heffernan    schedule 12.05.2014
comment
Большое спасибо! Использование TEncoding.UTF8.GetString (Bytes) - это ответ! К счастью, мобильное приложение и сервер не очень часто используют один и тот же код :) Еще раз спасибо! - person tikskit; 12.05.2014

Вместо

Utf8 : String;

Использовать

Utf8 : Utf8String;

на клиенте. Тогда преобразование будет автоматическим.

РЕДАКТИРОВАТЬ: поскольку клиент находится на мобильной платформе, а Embarcadero решил исключить 8-битные строки в мобильных компиляторах, вышеуказанное не будет работать в этом конкретном случае. Но в других случаях, когда у вас есть 8-битная строка в кодировке UTF-8, Utf8String можно использовать для беспрепятственного преобразования туда и обратно между строками UTF-8 и Unicode без необходимости использования явных функций преобразования UTF-8. Просто используйте это как

UnicodeStringVariable := Utf8StringVariable;

or

Utf8StringVariable := UnicodeStringVariable;

и компилятор вставит соответствующее преобразование.

person HeartWare    schedule 12.05.2014
comment
Такого типа Utf8String нет, у меня есть необъявленный идентификатор E2003: 'Utf8String'. Думаю, это из-за того, что клиент - это мобильное приложение, а не десктоп. Кстати, как это возможно, что на Mobile есть функция UTF8ToUnicodeString, для которой требуется параметр типа _RawByteStr. Но нет типа _RawByteStr? Я не могу объявить переменную этого типа! - person tikskit; 12.05.2014
comment
_RawByteStr соответствует типу RawByteString, представленному в Delphi 2009. 8-битные строковые типы все еще существуют в мобильных компиляторах, они просто скрыты, поэтому пользовательский код больше не может использовать их напрямую, но внутренние компоненты RTL (особенно модуль System) все еще работают. - person Remy Lebeau; 12.05.2014
comment
@tikskit: А! (Не) известные 8-битные строки, которые были удалены из мобильного компилятора. Вы можете применить патч, чтобы повторно представить их: andy.jgknet.de/blog/2013/10/the-return-of-the-byte-strings, хотя это может быть лишь временной мерой. - person HeartWare; 12.05.2014
comment
@HeartWare спасибо за ссылку. Я собираюсь применить этот патч andy.jgknet.de/blog/2014/05/ide-fix-pack-5-6-for-xe6-released в надежде, что он также включает эти исправления - person tikskit; 13.05.2014