Delphi в мире Юникода

Delphi и кодировка Unicode Часть

Nick Hodges, Embarcadero
Перевод: Виктор Роднев, www.interface.ru
Оригинал: Delphi in a Unicode World Part II: New RTL Features and Classes to Support Unicode
В данной статье рассматриваются новые функции библиотеки Tiburon Runtime Library, которые помогают обрабатывать строки Unicode.

Дополнительная поддержка Unicodeбиблиотекой RTL

В библиотеку RTL добавлен ряд подпрограмм, которыми поддерживается использование строк Unicode.

Другие функции библиотеки RTL, связанные с кодировкой Unicode

Есть ряд других подпрограмм для преобразования строк из одной кодовой страницы в другую. В их числе:
  • UnicodeStringToUCS4String
  • UCS4StringToUnicodeString
  • UnicodeToUtf8
  • Utf8ToUnicode

  • Кроме того, в библиотеке RTL также появился тип RawByteString, который представляет собой тип строки, не связанный ни с какой кодировкой:   RawByteString = type AnsiString($FFFF);
    Тип RawByteString позволяет передавать строковые данные для любой кодовой страницы без каких-либо преобразований последней. Он особенно полезен для подпрограмм, для которых кодировка не имеет значения, например для побайтового поиска в строке. Обычно это означает, что параметры подпрограмм, которыми обрабатываются строки без связи с их кодовыми страницами, должны иметь тип RawByteString. Тип RawByteString следует присваивать переменным как можно реже, если вообще когда-либо, так как это может привести к непредсказуемому поведению и возможной потере данных.
    Как правило, присвоения типов строк совместимы друг с другом.
    Например, присвоение MyUnicodeString := MyAnsiString;
    будет выполнено, как и ожидалось - содержимое строки типа AnsiString будет помещено в строку типа UnicodeString. Как правило, можно присваивать один тип строки строке другого типа, и компилятор выполнит необходимые преобразования, если возможно.
    Однако некоторые преобразования могут привести к потере данных, и необходимо остерегаться при перемещении из строки одного типа, которая включает данные в кодировке Unicode, в другую, тип которой не поддерживает Unicode. Например, можно присваивать тип UnicodeString строке типа AnsiString, но если строка типа UnicodeString содержит символы, которые не отображаются в активной в данный момент кодовой странице ANSI, то эти символы будут потеряны при преобразовании. Рассмотрим следующий код: procedure TForm88.Button4Click(Sender: TObject); var   U: UnicodeString;   A: AnsiString; begin   U := 'This is a UnicodeString';   A := U;   Memo1.Lines.Add(A);   U := 'Добро пожаловать в мир Юникода с использованием Дельфи 2009!!';   A := U;   Memo1.Lines.Add(A); end;
    Результат выполнения этого кода, если текущая кодовая страница ОС - 1252, будет следующим: This is a UnicodeString ????? ?????????? ? ??? ??????? ? ?????????????? ?????? 2009!!
    Как можно видеть, при назначении типа UnicodeString строке типа AnsiString информация теряется,  так как символы кириллицы не отображаются кодовой страницей Windows-1252. Причина такого результата - строка UnicodeString содержала символы, не представимые в кодовой странице AnsiString, эти символы были потеряны и заменены вопросительными знаками при назначении типа UnicodeString строке типа AnsiString.

    Класс TCharacter

    Библиотека Tiburon RTL включает новый класс TCharacter, который описывается в модуле Character. Это закрытый класс, который полностью состоит из статичных функций класса. Разработчикам не следует создавать экземпляры класса TCharacter, предпочтительнее просто вызывать его статические методы класса напрямую. Функции этого класса позволяют, в числе прочего, выполнять:
  • преобразование символов в верхний или нижний регистр;
  • определение типа данного символа, то есть является ли символ буквой, цифрой, знаком препинания и так далее.

  • Классом TCharacter используются стандарты, установленные организацией Unicode consortium.
    Разработчики могут использовать класс TCharacter для выполнения многих действий, выполнявшихся до этого с наборами символов. Например, следующий программный код: uses Character; begin if MyChar in ["a"..."z", "A"..."Z"] then begin   ... end; end; можно легко заменить кодом: uses   Character; begin if TCharacter.IsLetter(MyChar) then begin     ... end; end;
    Модуль Character содержит также ряд автономных функций, которые выполняют функции каждой из функций класса TCharacter, так что если предпочтительнее простой вызов функции, приведенный выше программный код можно переписать как: uses   Character; begin if IsLetter(MyChar) then begin     ... end; end;
    Таким образом, класс TCharacter можно использовать для выполнения многих действий или проверки символов, которая может понадобиться.
    Кроме того, класс TCharacter содержит методы класса, позволяющие определить, является ли символ верхним или нижним суррогатом суррогатной пары.

    Класс TEncoding

    Библиотека Tiburon RTL также включает новый класс TEncoding. Его назначение - определить конкретный тип кодировки символов, чтобы можно было сообщить библиотеке VCL, какой тип кодировки необходимо использовать в конкретных ситуациях.
    Например, пусть есть экземпляр TStringList, содержащий текст, который необходимо записать в файл. Ранее необходимо было бы написать begin   ...   MyStringList.SaveToFile("SomeFilename.txt");    ... end;
    и файл записался бы, по умолчанию, в кодировке ANSI. Данный код по-прежнему хорошо работает - файл запишется им в кодировке ANSI, как и раньше, но теперь, когда в Delphi поддерживаются строковые данные в кодировке Unicode, разработчикам может потребоваться записать строковые данные в какой-либо конкретной кодировке. Поэтому оператор SaveToFile (а также LoadFromFile) теперь имеет дополнительный второй параметр, которым определяется используемая кодировка: begin   ...   MyStringList.SaveToFile("SomeFilename.txt", TEncoding.Unicode);    ... end;
    При выполнении приведенного выше кода файл будет записан как текстовый файл в кодировке Unicode (UTF-16).
    Классом TEncoding будет также преобразовываться заданный набор байтов из одной кодировки в другую, извлекаться информация о байтах и/или символах в заданной строке или массиве символов, преобразовываться любая строка в массив array of byte (TBytes) и выполняться другие функции, которые могут потребоваться для конкретной кодировки заданной строки или заданного массива символов.
    Класс TEncoding включает следующие свойства класса, дающие доступ к экземпляру TEncoding заданной кодировки:     class property ASCII: TEncoding read GetASCII;     class property BigEndianUnicode: TEncoding read GetBigEndianUnicode;     class property Default: TEncoding read GetDefault;     class property Unicode: TEncoding read GetUnicode;     class property UTF7: TEncoding read GetUTF7;     class property UTF8: TEncoding read GetUTF8;
    Свойство Default ссылается на активную кодовую страницу ANSI. Свойство Unicode ссылается на UTF-16.
    Класс TEncoding также включает функцию class function TEncoding.GetEncoding(CodePage: Integer): TEncoding;
    которая будет возвращать экземпляр TEncoding, соответствующий кодовой странице, переданной в параметре.
    Кроме того, он включает следующую функцию: function GetPreamble: TBytes;
    которая будет возвращать правильный маркер порядка байтов для заданной кодировки.
    Класс TEncoding также представляет собой интерфейс, совместимый с классом .Net Encoding.

    Получение массива байтов TBytesиз строк

    В составе библиотеки RTL есть также набор перегружаемых подпрограмм для извлечения из строки массива байтов. Как будет показано в , рекомендуется использовать в качестве буфера данных массив TBytes, а не строку. Библиотека RTL упрощает это за счет перегружаемых версий функции BytesOf(), принимающей в качестве параметра разные типы строк.

    SetCodePage

    Функция SetCodePage, объявленная в модуле System.pas как procedure SetCodePage(var S: AnsiString; CodePage: Word; Convert: Boolean);
    представляет собой новую функцию RTL, которой задается новая кодовая страница для строки типа AnsiString. Необязательным параметром Convert определяется, следует ли преобразовать сами данные строки в заданную кодовую страницу. Если значение параметра Convert - False, то для строки просто будет изменена кодовая страница. Если значение параметра Convert - True, то данные передаваемой строки будут преобразованы в заданную кодовую страницу.
    Функция SetCodePage должна использоваться редко и с большой осторожностью. Обратите внимание, что, если кодовая страница не соответствует существующим данным строки (то есть значение параметра Convert  - False), то результаты могут оказаться непредсказуемыми. Кроме того, если существующие данные строки преобразованы, а в новой кодовой странице не представлен заданный исходный символ, данные могут потеряться.

    StringCodePage

    Подпрограммой StringCodePage будет возвращаться значение Word, которое соответствует кодовой странице для заданной строки.
    Рассмотрим следующий код: procedure TForm88.Button2Click(Sender: TObject); type   // Кодовая страница для ANSI-кириллицы - 1251   CyrillicString = type AnsiString(1251); var   A: AnsiString;   U: UnicodeString;   U8: UTF8String;   C: CyrillicString; begin   A := 'This is an AnsiString';   Memo1.Lines.Add('AnsiString Codepage: ' + IntToStr(StringCodePage(A)));   U := 'This is a UnicodeString';   Memo1.Lines.Add('UnicodeString Codepage: ' + IntToStr(StringCodePage(U)));   U8 := 'This is a UTF8string';   Memo1.Lines.Add('UTF8string Codepage: ' + IntToStr(StringCodePage(U8)));   C := 'This is a CyrillicString';   Memo1.Lines.Add('CyrillicString Codepage: ' + IntToStr(StringCodePage(C))); end;
    Результатом выполнения приведенного выше кода будет: The Codepage for an AnsiString is: 1252 The Codepage for an UnicodeString is: 1200 The Codepage for an UTF8string is: 65001 The Codepage for an CyrillicString is: 1251

    StringElementSize

    Подпрограммой StringElementSize возвращается типичный размер элемента (элемента кода) в заданной строке. Рассмотрим следующий код: procedure TForm88.Button3Click(Sender: TObject); var   A: AnsiString;   U: UnicodeString; begin   A := 'This is an AnsiString';   Memo1.Lines.Add('The ElementSize for an AnsiString is: ' + IntToStr(StringElementSize(A)));   U := 'This is a UnicodeString';   Memo1.Lines.Add('The ElementSize for an UnicodeString is: ' + IntToStr(StringElementSize(U))); end;
    Результатом выполнения приведенного выше кода будет: The ElementSize for an AnsiString is: 1 The ElementSize for an UnicodeString is: 2

    TStringBuilder

    Библиотека RTL теперь включает класс TStringBuilder. Его назначение ясно из его названия - это класс, предназначенный для создания строк. Класс TStringBuilder содержит большое количество перегружаемых функций для добавления, замены и вставки содержимого в заданную строку.  Этот класс упрощает создание единых строк из множества различных типов данных. Каждая из функций Append, Insert и Replace возвращает экземпляр класса TStringBuilder, поэтому их можно легко объединять для создания единой строки.
    Например, можно использовать класс TStringBuilder вместо усложненного оператора Format. Например, можно написать следующий программный код: procedure TForm86.Button2Click(Sender: TObject); var   MyStringBuilder: TStringBuilder;   Price: double; begin   MyStringBuilder := TStringBuilder.Create('');   try     Price := 1.49;     Label1.Caption := MyStringBuilder.Append('The apples are $').Append(Price).              AAppend(' a pound.').ToString;   finally     MyStringBuilder.Free;   end; end;
    Класс TStringBuilder также представляет собой интерфейс, совместимый с классом .Net StringBuilder.
    Объявление новых типов строк
    Компилятор Tiburon позволяет объявить собственный тип строки, связанный с заданной кодовой страницей. Доступно любое число кодовых страниц. Например, если необходим тип строки, соответствующий кодировке ANSI-кириллице, можно объявить: type   // Кодовая страница для ANSI-кириллицы - 1251   CyrillicString = type Ansistring(1251);
    И новый тип строки будет соответствовать кодовой странице кириллицы.


    Библиотека Tiburon' s Runtime Library теперь полностью поддерживает новый тип строки UnicodeString. В нее входят новые классы и подпрограммы для обработки и преобразования строк в кодировке Unicode, для управления кодовыми страницами и для упрощения перехода с более ранних версий.
    В будут рассмотрены специальные кодовые конструкции, которые необходимы, чтобы выяснить: готов ли тот или иной программный код к переходу на кодировку Unicode.

    Части, которые должны быть проверены

    В этой части статьи описаны различные конструкции, которые должны быть проверены в существующем коде для совместимости с Юникодом. Поскольку Char теперь является WideChar, данные о байтовом размере символьного массива или строки могут быть неверными. Ниже приведены несколько примеров конструкций кода, которые нужно изучить, чтобы убедиться в их совместимости с новым типом UnicodeString.

    Части, которые должны "работать прямо так"

    Здесь рассказывается о тех частях кода, которые будут продолжать работать и не потребуют никаких изменений для корректной работы с новой UnicodeString. VCL и RTL были полностью обновлены, чтобы работать в Delphi 2009 так, как и всегда. С маленькими-маленькими оговорками так оно и есть. К примеру, TStringList теперь полностью поддерживает Юникод, и весь существующий код, в котором используется TStringList, должен работать так же, как и раньше. Кроме того, TStringList был улучшен для работы специально с Юникодом, поэтому если Вы хотите использовать новую функциональность, Вы можете это сделать, но если это Вам не нужно - можете вообще о ней не думать.

    CreateProcessW

    Юникод-версия CreateProcess (CreateProcessW) работает немного иначе, нежели ANSI-версия. Цитата MSDN из описания параметра lpCommandLine:
    "Юникод-версия это функции, CreateProcessW, может изменить содержимое этой строки. Таким образом, этот параметр не может указывать на память только-для-чтения (то есть быть константной переменной или символьной строкой). Если этой параметр - константа, функция может вызвать ошибку доступа."
    Из-за этого существующий код, вызывающий CreateProcess, может начать выдавать ошибки доступа (Access Violations) после компиляции в Delphi 2009.
    Примеры такого кода:

    Delphi в мире Юникода Часть III: Юникодификация Вашего кода

    Nick Hodges, Embarcadero
    Перевод: Владимир Паранин, www.interface.ru
    Оригинал: Delphi in a Unicode World Part III: Unicodifying Your Code
    В этой статье рассказывается о том, что Вам нужно сделать, чтобы подготовить свой код к Delphi 2009.
    В этой серии было сказано, что Delphi 2009 по умолчанию будет использовать строку, основанную на UTF-16. В результате некоторые части существующего кода могут потребовать изменений. В основном, большая часть существующего кода будет прекрасно работать в Delphi 2009. Как Вы увидите, основные изменения в коде касаются только очень специфических, даже эзотерических, моментов. Как бы там ни было, нужно рассмотреть те особые части кода, которые, скорее всего, придется редактировать. Также нужно будет проверить результаты работы такого кода, чтобы убедиться, что он правильно работает с UnicodeString.
    Например, любой код, работающий со строками и выполняющий операции с указателями на них, должен быть изучен на совместимость с Юникодом. Говоря более конкретно, любой код:
  • считающий, что SizeOf(Char)=1;
  • считающий, что длина строки равна количеству байт в строке;
  • который пишет и читает строки из какого-либо постоянного хранилища или использует строку как буфер для данных

  • должен быть проанализирован, чтобы убедиться, что эти особенности не влияют на работу. Код, который пишет и читает из какого-либо постоянного хранилища, должен быть проверен на то, что считывается или пишется правильное количество байт, так как один байт больше не является одним символом.
    Как правило, любые изменения в коде являются достаточно простыми и могут быть сделаны с минимальными усилиями.

    Delphi в мире Юникода

    При вызове GetProcAddress всегда следует использовать PAnsiChar (в SDK нет функции с суффиксом "W"). Например:  procedure CallLibraryProc(const LibraryName, ProcName: string);  var    Handle: THandle;    RegisterProc: function: HResult stdcall;  begin    Handle := LoadOleControlLibrary(LibraryName, True);    @RegisterProc := GetProcAddress(Handle, PAnsiChar(AnsiString(ProcName)));  end;
    Примечание: Windows.pas содержит перегруженный метод, который выполняет это преобразование.

    Индексация в строках

    Индексация в строках работает точно так же, как и прежде, поэтому код, индексирующий элементы строк, не требует изменений: var S: string; C: Char; begin S := "This is a string"; C := S[1];  // C будет содержать "T", но C это, конечно же, WideChar end;

    Использование буквенных символов

    Следующий код   if Edit1.Text[1] = #128 then
    распознает символ Евро и в итоге даст True в большинстве кодовых страниц ANSI. Однако в Delphi 2009 он даст False, так как #128 - это символ Евро в большинстве ANSI-страниц, а в Юникоде это - управляющий символ. В Юникоде символом Евро имеет код #$20AC.
    При переходе на Delphi 2009 разработчикам следует заменить все коды символов со #128 по #255 на их буквенные значения, тогда:   if Edit1.Text[1] = '€' then
    будет работать так же, как #128 в ANSI, но будет нормально функционировать (то есть распознавать символ Евро) в Delphi 2009 (где '€' имеет код #$20AC)

    Использование FillChar

    Вызов FillChar также нужно проверить при работе со строками и символами. Рассмотрим следующий код:  var    Count: Integer;    Buffer: array[0..255] of Char;  begin    // Существующий код - неправильный, потому что string = UnicodeString    Count := Length(Buffer);    FillChar(Buffer, Count, 0);       // Правильный код для Юникода - любой из вариантов верный    Count := SizeOf(Buffer);                // <<-- Задание размера буфера в байтах    Count := Length(Buffer) * SizeOf(Char); // <<-- Задание размера буфера в байтах    FillChar(Buffer, Count, 0);  end;
    Length возвращает размер в символах, но FillChar ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).
    Кроме того, так как по умолчанию размер Char равен 2, FillChar заполнит строку байтами, а не символами, как раньше.
    Пример: var   Buf: array[0..32] of Char; begin   FillChar(Buf, Length(Buf), #9); end;
    Это заполнит массив символами с кодом не $09, а $0909. Чтобы получить прежний результат, код нужно изменить: var   Buf: array[0..32] of Char; begin   ..   StrPCopy(Buf, StringOfChar(#9, Length(Buf)));   .. end;

    Использование функции Chr

    Существующий код, превращающий значение типа integer в Char может использовать функцию Chr. Это может привести к следующей ошибке: [DCC Error] PasParser.pas(169): E2010 Incompatible types: 'AnsiChar' and 'Char'
    Если в коде, использующем функцию Chr, имеется присвоение ее результата переменной типа AnsiChar, то эту ошибку можно легко исключить, заменив функцию Chr преобразованием в тип AnsiChar.
    То есть, такой код: MyChar := chr(i);
    можно заменить таким: MyChar := AnsiChar(i);

    Использование Move

    Следует проанализировать использование функции Move при работе со строками или символьными массивами. Рассмотрим следующий код:  var    Count: Integer;    Buf1, Buf2: array[0..255] of Char;  begin    // Существующий код - неправильный, потому что string = UnicodeString    Count := Length(Buf1);    Move(Buf1, Buf2, Count);       // Правильный код для Юникода    Count := SizeOf(Buf1);                // <<-- Задание размера буфера в байтах    Count := Length(Buf1) * SizeOf(Char); // <<-- Задание размера буфера в байтах    Move(Buf1, Buf2, Count);  end;
    Length возвращает размер в символах, но Move ожидает, что Count будет в байтах. В этом случае вместо Length нужно использовать SizeOf (или нужно умножить Length на размер Char).

    Использование преобразований к PChar() для работы с указателями при указании на не символьные типы

    В предыдущих версиях не все типизированные указатели поддерживали арифметические операции. Из-за этого для выполнения арифметических операций над такими указателями они преобразовывались к PChar. В Delphi 2009 арифметика для указателей может быть включена директивой компилятора и она специально включена для типа PByte. Таким образом, если у Вас есть подобный код, преобразующий указатель к PChar для выполнения арифметических операций над ним:  function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;  begin    if (Node = FRoot) or (Node = nil) then      Result := nil    else      Result := PChar(Node) + FInternalDataOffset;  end; Вы должны заменить его использованием PByte вместо PChar:  function TCustomVirtualStringTree.InternalData(Node: PVirtualNode): Pointer;  begin    if (Node = FRoot) or (Node = nil) then      Result := nil    else      Result := PByte(Node) + FInternalDataOffset;  end;
    В приведенном выше куске кода Node не содержит символьных данных. Он преобразовывается к PChar только для доступа к данным, расположенным через заданное число байт после Node. Раньше это работало, так как SizeOf(Char) = SizeOf(Byte). Теперь это работать не будет. Чтобы сделать работу кода правильной, следует использовать PByte вместо PChar. Если оставить все без изменений, Result будет указывать на некорректные данные.

    Использование строк в качестве буферов данных

    Часто при работе с данными строки используются как буферы. Это делается часто, потому что это просто - работа со строками проста и понятна. Однако, существующий код, который так делает, в большинстве случаев потребует дополнительной настройки, исходя из того, что string это теперь UnicodeString.
    Есть несколько способов разобраться с кодом, который использует строки как буферы данных. Первый - это просто объявить переменную, используемую в качестве буфера, как AnsiString вместо string. Если для работы с байтами буфера в коде используются Char"ы - объявите эти переменные как AnsiChar. Если Вы выберете этот путь, весь Ваш код будет работать, как и прежде, но Вы должны помнить: все переменные, работающие с таким строковым буфером, должны быть ANSI-типа.
    Второй, более верный путь, - это преобразовать Ваш буфер из строкового типа в массив байтов или TBytes. Тип TBytes был создан специально для этой цели и работает так же, как и раньше, если использовался тип string.

    Код для проверки

    Вот список возможных мест в коде, которые Вам следует найти, чтобы проверить, готов ли Ваш код к правильному использованию Юникода:
  • найти любое использование "of Char" или "of AnsiChar", чтобы проверить, что буферы корректно работают с Юникодом;
  • найти "string[" и проверить, что символ, полученный по ссылке, заносится в Char (то есть в WideChar).
  • проверить неявную работу с AnsiString, AnsiChar и PAnsiChar, убедиться, что она по-прежнему нужна и правильно работает;
  • найти неявное использование ShortString, убедиться, что оно по-прежнему требуется и правильно работает;
  • найти вызовы Length( и проверить, чтобы там не подразумевалось, что Length это то же самое, что SizeOf;
  • найти вызовы Copy(, Seek(, Pointer(, AllocMem( и GetMem( и проверить, чтобы они правильно работали со строками или символьными массивами.

  • Это типичные конструкции, которые, возможно, придется изменить для поддержки нового типа UnicodeString.

    LeadBytes

    Замените такой код:  if Str[I] in LeadBytes then
    использованием функции IsLeadChar:  if IsLeadChar(Str[I]) then

    Length/Copy/Delete/SizeOf для строк

    Функция Copy будет работать, как всегда, без изменений. То же самое относится к Delete и всем остальным процедурам работы со строками, основанными на SysUtils.
    Вызов Length(SomeString), как и всегда, вернет количество элементов в переданной строке.
    Вызов SizeOf для любого идентификатора строки вернет 4, так как все строковые объявления - это ссылки и размер указателя равен 4.
    Вызов Length для любой строки вернет количество элементов в этой строке.
    Рассмотрим следующий код: var   S: string; begin     S:= 'abcdefghijklmnopqrstuvwxyz';     WriteLn('Length = ', Length(S));     WriteLn('SizeOf = ', SizeOf(S));     WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));     ReadLn; end.
    В результате его выполнения будет выведено следующее:
    Length/Copy/Delete/SizeOf для строк


    Методы Read/ReadBuffer для TStream

    Вызов TStream.Read/ReadBuffer также следует рассмотреть, если используются строки или символьные массивы. Рассмотрим следующий код:  var    S: string;    L: Integer;    Stream: TStream;    Temp: AnsiString;  begin    // Существующий код - неправильный, потому что string = UnicodeString    Stream.Read(L, SizeOf(Integer));    SetLength(S, L);    Stream.Read(Pointer(S)^, L);       // Правильный код для Юникод-данных    Stream.Read(L, SizeOf(Integer));    SetLength(S, L);    Stream.Read(Pointer(S)^, L * SizeOf(Char));  // <<-- Задание размера буфера в байтах       // Правильный код для ANSI-данных    Stream.Read(L, SizeOf(Integer));    SetLength(Temp, L);              // <<-- Используем временную AnsiString    Stream.Read(Pointer(Temp)^, L * SizeOf(AnsiChar));  // <<-- Задание размера буфера в байтах    S := Temp;                       // <<-- Расширим строку до Юникода  end;
    Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream"е.

    MultiByteToWideChar

    Вызовы MultiByteToWideChar можно просто убрать и заменить простым присвоением. Пример использования MultiByteToWideChar:  procedure TWideCharStrList.AddString(const S: string);  var    Size, D: Integer;  begin    Size := SizeOf(S);    D := (Size + 1) * SizeOf(WideChar);    FList[FUsed] := AllocMem(D);    MultiByteToWideChar(0, 0, PChar(S), Size, FList[FUsed], D);   Inc(FUsed);  end;
    А после перехода к Юникоду этот код был изменен, чтобы компилироваться как для ANSI, так и для Юникода: procedure TWideCharStrList.AddString(const S: string); var    L, D: Integer; begin    FList[FUsed] := StrNew(PWideChar(S));    Inc(FUsed);  end;

    Обычное использование строковых типов

    В основном, код, использующий строковый тип, будет работать, как и прежде. Нет необходимости переобъявлять строковые переменные в тип AnsiString, за исключением тех случаев, которые описаны ниже. Объявления строк должны быть изменены на AnsiString во всех местах, где строки используются в качестве буфера для данных.

    Параметры с вариантными массивами

    Если Ваш код использует TVarRec для работы с параметром - вариантным массивом - возможно, Вам придется отредактировать его для работы с UnicodeString. Для этого теперь есть новый тип vtUnicodeString, хранящий данные из UnicodeString. Рассмотрим следующий кусок из DesignIntf.pas, показывающий, в каком случае следует добавить новый код для работы с UnicodeString.  procedure RegisterPropertiesInCategory(const CategoryName: string;    const Filters: array of const); overload;  var    I: Integer;  begin    if Assigned(RegisterPropertyInCategoryProc) then      for I := Low(Filters) to High(Filters) do        with Filters[I] do          case vType of            vtPointer:              RegisterPropertyInCategoryProc(CategoryName, nil,                PTypeInfo(vPointer), );            vtClass:              RegisterPropertyInCategoryProc(CategoryName, vClass, nil, );            vtAnsiString:              RegisterPropertyInCategoryProc(CategoryName, nil, nil,                string(vAnsiString));            vtUnicodeString:              RegisterPropertyInCategoryProc(CategoryName, nil, nil,                string(vUnicodeString));          else            raise Exception.CreateResFmt(@sInvalidFilter, [I, vType]);          end;  end;

    Передача константного выражения

    const cMyExe = 'foo.exe' begin CreateProcess(nil, cMyExe, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo); end;

    Передача строки с числом ссылок (Reference Count) -1:

    const   cMyExe = 'foo.exe' var   sMyExe: string; begin   sMyExe := cMyExe;   CreateProcess(nil, PChar(sMyExe), nil, nil, False, 0, nil, nil, StartupInfo,    ProcessInfo); end;

    Передача строковой константы

       CreateProcess(nil, 'foo.exe', nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);

    Работа с указателями для PChar

    Работа с указателями для PChar будет выполняться, как и раньше. Компилятору известен размер PChar, поэтому код, подобный приведенному ниже, будет работать, как и ожидается: var p: PChar; MyString: string; begin   ...   p := @MyString[1]; Inc(p); ... end;
    Этот код будет работать точно так же, как и в предыдущих версиях Delphi, но, конечно, с другими типами данных: PChar это теперь PWideChar и MyString - это теперь UnicodeString.

    Runtime Library

    Дополнения к Runtime Library были подробно рассмотрены в .
    В той статье не упоминался новый модуль, добавленный в RTL - AnsiString.pas. Этот модуль существует для обратной совместимости с кодом, который использует или требует для своей работы AnsiString.
    Код Runtime Library выполняется как обычно, и в основном не требует изменений. Части, которые нужно изменить, описаны ниже.

    SaveToFile/LoadFromFile

    Вызовы SaveToFile и LoadFromFile можно было бы отнести к предыдущей части статьи (Части, которые должны "работать прямо так"), если бы они выполняли чтение и запись так же, как они делали это раньше. Однако Вам может понадобиться использование новых перегруженных версий этих процедур, если Вы решили работать с Юникод-данными.
    К примеру, TStrings теперь включает следующий набор перегруженных методов: procedure SaveToFile(const FileName: string); overload; virtual; procedure SaveToFile(const FileName: string; Encoding: TEncoding); overload; virtual;
    Второй метод - это новая перегрузка, принимающая кодировку в качестве параметра, который задает, каким образом данные будут записаны в файл. (В Вы можете прочитать описание типа TEncoding.) Если Вы вызовете первый метод, строковые данные будут записаны так же, как это делалось обычно - как ANSI-данные. Благодаря этому уже существующий код будет работать точно так же, как и всегда.
    Однако если Вам нужно записать текст в формате Юникод, то нужно вызвать второй вариант метода, передав ему в параметре соответствующее значение типа TEncoding. Если не сделать этого, строки будут записаны как ANSI-данные, что, скорее всего, приведет к потере информации.
    Таким образом, наилучший способ в этом случае - проанализировать вызовы SaveToFile и LoadFromFile и добавить к ним второй параметр, чтобы показать, каким образом нужно сохранить или загрузить данные. Если Вы считаете, что никогда не будете добавлять или использовать Юникод-строки, то можете оставить все, как есть.

    ShortString

    ShortString осталась неизменной, как по функциям, так и по объявлению, она будет работать, как и раньше.
    Объявления ShortString выделяют буфер для заданного количества AnsiChar"ов. Такой код: var   S: string[26]; begin     S:= 'abcdefghijklmnopqrstuvwxyz';     WriteLn('Length = ', Length(S));     WriteLn('SizeOf = ', SizeOf(S));     WriteLn('TotalBytes = ', Length(S) * SizeOf(S[1]));     ReadLn; end.
    выведет на экран следующее:
    ShortString

    Обратите внимание, что общий размер алфавита - 26, это говорит о том, что переменная содержит AnsiChar"ы.
    Рассмотрим также и такой код: type TMyRecord = record   String1: string[20];   String2: string[15]; end;
    Это запись будет расположена в памяти точно так же, так и раньше - это будет запись из двух AnsiString"ов, содержащих AnsiChar"ы. Если у Вас есть File of Rec из записей, содержащих ShortString"и, то приведенный выше код будет работать, как и раньше, и любое чтение или запись не потребует никаких изменений.
    Однако помните, что Char - это теперь WideChar, поэтому если Вы используете код, который читает такие записи из файла и потом делаете что-то вроде: var MyRec: TMyRecord; SomeChar: Char; begin // Чтение MyRec из файла... SomeChar := MyRec.String1[3]; ... end;
    то Вы должны помнить, что SomeChar превратит AnsiChar в String1[3] в WideChar. Если Вам нужно, чтобы этот код работал, как раньше, измените объявление SomeChar: var MyRec: TMyRecord; SomeChar: AnsiChar; // Теперь объявлен как AnsiChar для символа из ShortString begin // Чтение MyRec из файла... SomeChar := MyRec.String1[3];  ... end;

    Символьные множества

    Наверное, самой распространенной идиомой, которая может создать проблемы компилятору, является использование символов в множествах. Раньше, когда символ занимал один байт, хранение символов в множествах не создавало никаких трудностей. Но теперь Char объявлен как WideChar, и поэтому больше не может храниться в множестве. Поэтому, если у Вас есть код наподобие этого: procedure TDemoForm.Button1Click(Sender: TObject); var   C: Char; begin   C := Edit1.Text[1];   if C in ['a'..'z', 'A'..'Z'] then   begin    Label1.Caption := 'It is there'; end; end;
    и Вы скомпилируете его, то получите предупреждение, которое будет выглядеть примерно так: [DCC Warning] Unit1.pas(40): W1050 WideChar reduced to byte char in set expressions.  Consider using 'CharInSet' function in 'SysUtils' unit.
    Если хотите, можете оставить код неименным - компилятор будет "знать", что Вы пытаетесь сделать и сгенерирует правильный код. Однако, если Вы хотите избавиться от этого предупреждения, то можете использовать новую функцию CharInSet:   if CharInSet(C, ['a'..'z', 'A'..'Z']) then   begin    Label1.Caption := 'It is there';   end;
    Функция CharInSet вернет булевское значение и код скомпилируется без предупреждений компилятора.

    SysUtils.AppendStr

    Этот метод может использовать только AnsiString, нет его перегруженной версии для UnicodeString.
    Замените вызовы вроде этого:  AppendStr(String1, String2);
    таким кодом:  String1 := String1 + String2;
    Или, еще лучше, используйте новый класс TStringBuilder для соединения строк.

    TMemoryStream

    В тех случаях, когда для записи текста в файл используется TMemoryStream, важной является запись Byte Order Mark (BOM) в качестве начальных данных файла. Вот пример записи BOM в файл:  var    BOM: TBytes;  begin    ...    BOM := TEncoding.UTF8.GetPreamble;    Write(BOM[0], Length(BOM)); Весь пишущий код должен быть изменен на работу в кодировке UTF8 для Юникод-строк:  var    Temp: Utf8String;  begin    ...    Temp := Utf8Encode(Str); // <-- Str - это строка, записываемая в файл    Write(Pointer(Temp)^, Length(Temp));  //Write(Pointer(Str)^, Length(Str)); <-- это оригинальный вызов Write для записи строки в файл

    TStringStream

    TStringStream теперь происходит от нового типа TByteStream. TByteStream добавляет свойство Bytes, дающее прямой доступ к байтам из TStringStream. TStringStream продолжает работать, как и всегда, за исключением того, что строка, которую он хранит, теперь является Юникод-строкой.

    VCL

    Вся VCL поддерживает Юникод. Все существующие VCL-компоненты работают с момента установки, как всегда и было. Практически весь код, использующий VCL, будет нормально работать. Мы проделали огромную работу, чтобы быть уверенными, что VCL совместим как со старым кодом, так и с Юникодом. Обычный VCL-код, не выполняющий никаких особенных манипуляций со строками, будет работать как и раньше.

    Вызов SizeOf для буферов

    Вызов SizeOf при использовании символьных массивов должен быть проверен на корректность. Рассмотрим следующий код: procedure TDemoForm.Button1Click(Sender: TObject); var var   P: array[0..16] of Char; begin   StrPCopy(P, 'This is a string');   Memo1.Lines.Add('Length of P is ' +  IntToStr(Length(P)));   Memo1.Lines.Add('Size of P is ' +  IntToStr(SizeOf(P))); end;
    Вот что этот код выведет в Memo1: Length of P is 17 Size of P is 34
    В этом коде Length вернет количество символов в данной строке (плюс терминальный символ), а SizeOf вернет количество байтов, использованных этим массивом, в данном случае 34, то есть по два байта на символ. В предыдущих версиях Delphi этот код вернул бы 17 в обоих случаях.

    Write/WriteBuffer

    Как и в случае Read/ReadBuffer, использование TStream.Write/WriteBuffer следует проверить, если используются строки или символьные массивы. Рассмотрим следующий код:  var    S: string;    Stream: TStream;    Temp: AnsiString;  begin    // Существующий код - неправильный, потому что string = UnicodeString    Stream.Write(Pointer(S)^, Length(S));       // Правильный код для Юникод-данных    Stream.Write(Pointer(S)^, Length(S) * SizeOf(Char)); // <<-- Задание размера буфера в байтах       // Правильный код для ANSI-данных    Temp := S;          // <<-- Используем временную AnsiString    Stream.Write(Pointer(Temp)^, Length(Temp) * SizeOf(AnsiChar));// <<-- Задание размера буфера в байтах  end;
    Примечание: работа зависит от формата читаемых данных. Смотрите описание нового класса TEncoding, приведенное выше, для получения сведений о правильном кодировании текста в Stream"е.


    Вот и все потенциально узкие места в коде, которые нужно проверить на правильность для жизни в мире Юникода. Большая часть Вашего кода по-прежнему будет работоспособной. Большинство предупреждений, которые получит Ваш код от компилятора, можно легко исправить. Значительная часть моментов, которые нужно проверить, встречается достаточно редко, поэтому практически весь Ваш код будет работать просто прекрасно.

    Что такое кодировка Unicode

    Кодировка Unicode представляет собой схему кодировки символов, которая позволяет кодировать практически все алфавиты в виде одного набора символов. Кодировка Unicode позволяет компьютерам представлять текст для большинства систем письма в мире и обращаться с ним. Разработка кодировки Unicode поддерживается организацией Unicode Consortium и кодифицируется как стандарт. Проще говоря, кодировка Unicode представляет собой систему, позволяющую каждому использовать любые другие алфавиты. Представьте себе, существует даже версия Unicode клингонского языка.
    Данная серия статей не предназначается для того, чтобы дать полное и точное изложение того, что представляет собой код Unicode и как он работает; ее предназначение - объяснить, как использовать кодировку Unicode в Delphi 2009. В данной статье, первой из трех, будет объяснено, почему так важна кодировка  Unicode, и как в Delphi будет реализован новый тип строки UnicodeString.

    Delphi и кодировка UnicodeЧасть

    Nick Hodges, Embarcadero
    Перевод: Виктор Роднев, www.interface.ru
    Оригинал: Delphi in a Unicode World Part I: What is Unicode, Why do you need it, and How do you work with it in Delphi?

    Немного о терминологии

    Кодировка Unicode подразумевает использование некоторых новых терминов. Например, понятие "символ" в системе понятий Unicode менее точно, чем то, к которому, по-видимому, привыкли разработчики. Для кодировки Unicode более точным термином является "элемент кода". В Delphi 2009 размер SizeOf(Char) = 2, но даже и это не всегда. В зависимости от кодировки, это значение для данного символа может принимать значение больше двух байтов. Такие последовательности называются "суррогатными парами". Итак, элемент кода представляет собой уникальный код, назначенный элементу, определенному организацией Unicode Consortium (Unicode.org). Чаще всего это то же самое, что "символ", но не всегда.
    Еще один термин, который относится к кодировке Unicode - маркер порядка байтов (BOM), и это очень короткий префикс, используемый в начале текстового файла, чтобы указать тип кодировки, используемый для данного текстового файла. Новый класс TEncoding (будет рассмотрен в части II) содержит метод класса GetPreamble, который возвращает маркер порядка байтов для заданной кодировки.
    Теперь, после всех объяснений, рассмотрим, как в Delphi 2009 реализуется строка Unicode.

    Новый тип UnicodeString

    Тип строки по умолчанию в Delphi - новый тип UnicodeString. По умолчанию, тип UnicodeString имеет сходство с кодировкой UTF-16, той же самой, что используется в ОС Windows. В этом заключается отличие от предыдущей версии, в которой типом по умолчанию был тип AnsiString. Раньше в библиотеке Delphi RTL для обработки данных в формате Юникод использовался тип WideString, но этим типом, в отличие от типа AnsiString, не подсчитывалось количество ссылок, и поэтому он не являлся для разработчиков на Delphi строковым типом по умолчанию.
    Для Delphi 2009 был разработан новый тип UnicodeString, который объединяет возможности типов AnsiString и WideString. Строка UnicodeString может содержать как символы Unicode, так и символы ANSI. (Необходимо заметить, что будут сохранены типы AnsiString и WideString.) Типы Char и PChar будут преобразованы соответственно в типы WideChar и PWideChar. Следует также заметить, что не были исключены никакие типы строк. По-прежнему сохраняются и доступны для использования все использовавшиеся разработчиками типы.
    Однако в Delphi 2009 тип string по умолчанию эквивалентен типу UnicodeString. Кроме того, тип Char по умолчанию - тип WideChar, а тип PChar по умолчанию - PWideChar.
    То есть для компилятора задается следующий код: type   string = UnicodeString;   Char = WideChar;   PChar = PWideChar;
    Присвоение типа UnicodeString совместимо со всеми другими типами строк, однако присвоения между AnsiStrings и UnicodeStrings будут вызывать соответствующие преобразования типов. Таким образом, присвоение типа UnicodeString типу AnsiString может привести к потере данных. То есть если строка UnicodeString содержит данные со старшими байтами, преобразование такой строки в строку AnsiString приведет к потере старших байтов данных.
    Важно отметить, что новый тип UnicodeString имеет те же возможности, что и другие типы строк (за исключением, конечно, возможности содержать данные в кодировке Юникод). По-прежнему можно добавлять к ним любые строковые данные, индексировать их, объединять их знаком "+" и так далее.
    Например, экземпляры строки UnicodeString все так же позволяют индексировать символы. Рассмотрим следующий код:  var    MyChar: Char;    MyString: string;  begin    MyString := "This is a string";    MyChar := MyString[1];  end;
    Переменная MyChar будет содержать символ, находящийся на первой позиции индекса, то есть "T". Функциональное содержание данного кода никак не изменилось. Аналогично, при обработке данных в кодировке Unicode:  var    MyChar: Char;    MyString: string;  begin    MyString := "????";    MyChar := MyString[1];  end;
    Переменная MyChar будет содержать символ, находящийся на первой позиции индекса, то есть "?".
    Библиотека RTL содержит вспомогательные функции, которые позволяют пользователям выполнять явные преобразования кодовых страниц и размеров элементов. Если пользователь применяет функцию Move для массива символов, он не в состоянии учитывать размер элементов.
    Как можно предположить, данный новый тип строки приводит к отличиям от существующего кода. В кодировке Unicode один символ больше не представляется одним байтом. Более того, один символ не всегда означает даже два байта. В результате, возможно, придется внести некоторые изменения в существующий код. Однако ведется очень серьезная работа над тем, чтобы упростить такой переход, и есть уверенность, что его можно осуществить и продолжить работу достаточно быстро. В частях II и III этой статьи будет продолжено обсуждение нового типа UnicodeString , будет рассказано о некоторых новых функциях библиотеки RTL с поддержкой Юникода, а также о специфических средствах написания программ, которые могут понадобиться при программировании. Данная серия статей призвана помочь перейти на кодировку Unicode просто и без лишних усилий.

    Преимущества кодировки Unicode

    Среди многих новых функциональных возможностей, которые можно обнаружить в Delphi 2009, имеется полная поддержка кодировки Unicode всем продуктом. Тип строки по умолчанию в Delphi - теперь строка в кодировке Unicode. Так как система Delphi в значительной степени построена на языке Delphi, интегрированная среда разработки, компилятор, библиотеки RTL и VCL - все они полностью поддерживают кодировку Unicode.
    Переход на кодировку Unicode в Delphi вполне естественен. ОС Windows сама по себе полностью поддерживает кодировку Unicode, так что даже естественно, что приложениями, созданными для нее, строка Unicode используется как тип строки по умолчанию. И преимущества для разработчиков на Delphi не исчерпываются только возможностью использовать такой же тип строки, как и в Windows.
    Добавление поддержки кодировки Unicode дает большие возможности разработчикам на Delphi. Разработчики на Delphi могут теперь считывать, записывать, принимать, создавать, отображать и обрабатывать данные в кодировке Unicode - и все эти возможности встроены прямо в продукт. Приложения, только с небольшими или, в некоторых случаях, нулевыми изменениями можно подготовить к поддержке любых данных самого разработчика, клиентов и пользователей. Приложения, возможности которых ранее ограничивались данными в кодировке ANSI, теперь можно легко приспособить для обработки почти любого набора символов в мире.
    Разработчики на Delphi смогут теперь обслуживать глобальный рынок, разрабатывая свои приложения, даже если они не будут делать ничего специально для локализации или интернационализации своих приложений. ОС Windows сама по себе поддерживает много различных локализованных версий, и приложения Delphi должны иметь возможность адаптации и работы на компьютерах, на которых установлено большое число языковых настроек, поддерживаемых Windows, включая японскую, китайскую, греческую или русскую версии Windows. Пользователи программного обеспечения могут вводить в приложение текст или использовать имена путей не в кодировке ANSI. Приложения на основе кодировки ANSI в таких случаях не всегда работали бы так, как необходимо. Приложения Windows на основе Delphi с полной поддержкой Unicode смогут работать и в таких ситуациях. Даже если приложение не переведено на какие-либо другие разговорные языки, приложение, тем не менее, сохраняет нормальную работоспособность - независимо от языковой версии системы конечного пользователя.
    Что касается существующих приложений Delphi на основе кодировки ANSI, то возможность локализации приложений и их распространения на рынке приложений с поддержкой Unicode в перспективе очень велика. И если необходимо локализовать приложения, среда Delphi позволяет сделать это очень просто, особенно непосредственно во время разработки. Интегрированная среда перевода (ITE) позволяет переводить, компилировать и развертывать приложение непосредственно в интегрированной среде разработки IDE. Если необходимы услуги по переводу извне, среда IDE позволяет экспортировать проект в такой форме, которая может использоваться переводчиками вместе с развертываемым приложением External Translation Manager (Диспетчер внешних переводов). Данный инструментарий работает вместе со средой Delphi IDE, и для среды Delphi, и для среды C++Builder, позволяя локализовать приложения при согласованном и простом управлении процессом.
    Кодировка Unicode широко распространена в мире, и теперь разработчики на Delphi могут стать его частью естественным и органичным образом. Таким образом, если необходима возможность обработки данных в кодировке Unicode или есть стремление продавать приложения на растущих глобальных рынках, можно делать это с помощью Delphi 2009.

    к среде Delphi поддержки кодировки

    После добавления к среде Delphi поддержки кодировки Unicode как строки по умолчанию стало возможным принимать, обрабатывать и отображать фактически любые существующие в мире алфавиты или кодовые страницы. Приложения, создаваемые в среде Delphi 2009, будут способны принимать, отображать и обрабатывать текст в кодировке Unicode с достаточной простотой, и они будут работать гораздо лучше в любой языковой версии ОС Windows. Разработчики на Delphi смогут теперь с легкостью локализовать и переводить свои приложения, чтобы выйти на рынки, появиться на которых ранее им было гораздо сложнее. Теперь приложения Delphi могут функционировать везде в мире, где необходима поддержка Unicode.
    В будут рассмотрены изменения и обновления библиотеки Delphi Runtime Library, которые упрощают работу со строками в кодировке Unicode.

    

        Программирование: Языки - Технологии - Разработка