Sign in Join
Wiki copytemporary

FPC Unicode support/ru

English · Español · Français · Русский

Введение

До FPC 2.6.x включительно, RTL базировалась на своих аналогах в Turbo Pascal и Delphi 7. Это значит, что её функциональность была в основном построена на обработке строковых типов shortstring, ansistring и pchar. Ни один из этих типов не несёт какой либо информации о кодировке, связанной с ним, но неявно подразумевается, что строки находятся в "кодировке системы по умолчанию" и могут быть переданы на вызовы API операционной системы без преобразований.

В Delphi 2009, Embarcadero полностью переключил RTL на тип UnicodeString, использующий представление строк UTF-16. Дополнительно были введены типы AnsiString, содержащие маркировку кодовой страницы. Это означает, что строковые переменные AnsiStrings, относящиеся к таким типам, всегда содержат информацию о кодовой странице, указывающую, как данные должны быть интерпретированы.

Поддержка на уровне языка в FPC для таких строковых типов на настоящий момент уже доступна в текущих версиях компилятора "для разработчиков" (FPC 2.7.1/trunk). Поддержка на уровне RTL на настоящий момент неполная. На данной странице представлен обзор поведения таких строковых типов в отношении кодовых страниц, текущий уровень поддержки в RTL, а также возможные будущие пути, которыми эта поддержка может быть улучшена.

Обратная совместимость

Весь существующий код, работающий в рамках стандартного подхода (*), предназначенный для предыдущих версий FPC, изменений не потребует. Он должен продолжать работать без каких либо модификаций под новыми версиями FPC. Условия этого "стандартного" использования множества переменных с кодовой страницей по умолчанию и их значений описаны ниже.

(*) это в первую очередь означает: вы никогда не сохраняете данные в ansistring-ах с маркировкой кодовой страницы, отличающейся от системной кодовой страницы по умолчанию, и последовательно передаете строку в неизменном виде процедуре из FPC RTL. Например, в текущем коде Lazarus обычно производится вызов UTF8ToAnsi() перед передачей его строк подпрограммам FPC RTL.

Кодовые страницы

Кодовая страница определяет, как именно индивидуальные байты строки должны интерпретироваться, т.е. какая буква, символ или иной графический примитив соответствует каждому байту или последовательности байтов.

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

Идентификатор кодовой страницы всегда хранится как значение типа TSystemCodePage, являющимся псевдонимом (alias) для Word. Значение представляет собой соответствующую кодовую страницу, как это определено в [http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx Microsoft Windows]. Дополнительно используются следующие три специальных значения кодовых страниц:

Примечание: идентификаторы кодовых страниц отличаются от имён кодовых страниц, используемых в директивах {$codepage xxx} (доступных и в текущем стабильном FPC). Имена кодовых страниц для директивы берутся из специального перечня, в котором представлены такие идентификаторы, как cp866, cp1251 и utf8.

Настройки кодовой страницы

Модуль system содержит глобальные переменные, указывающих кодовую страницу по умолчанию, использующуюся для некоторых операций.

DefaultSystemCodePage

DefaultFileSystemCodePage

DefaultRTLFileSystemCodePage

Строки

Строковые/символьные типы

Shortstring

Кодовая страница данных типа shortstring неявно подразумевается как CP_ACP и, следовательно, будет равна текущему значению переменной DefaultSystemCodePage.

PAnsiChar/AnsiChar

Эти типы - то же самое, что и старые типы PChar/Char. Во всех режимах компилятора, кроме {$mode delphiunicode}, PChar/Char по-прежнему остаются псевдонимами для PAnsiChar/AnsiChar. Кодовая страница таких строк неявно подразумевается CP_ACP и, следовательно, будет равна текущему значению переменной DefaultSystemCodePage.

PWideChar/PUnicodeChar и WideChar/UnicodeChar

Эти типы остались неизменными. WideChar/UnicodeChar могут содержать одиночную кодовую единицу UTF-16, в то время как PWideChar/PUnicodeChar указывают либо на одну кодовую единицу, либо на массив кодовых единиц UTF-16.

В режиме {$mode delphiunicode}, PChar будет псевдонимом для PWideChar/PUnicodeChar и Char будет псевдонимом для WideChar/UnicodeChar.

UnicodeString/WideString

Эти типы остались такими же, как и в предыдущих версиях:

Ansistring

Семейство AnsiString - строковые типы со счетчиком ссылок и максимальной длиной high(SizeInt) байт. Кроме того, для каждой переменной такого типа сейчас сохраняется информация о кодовой странице, ассоциированной с данными.

Важнее всего понять, новое для переменной типа AnsiString в том, что она одновременно может обладать декларированной/статической/основной/по умолчанию кодовой страницей (далее по тексту статической кодовой страницей) и динамической кодовой страницей. Статическая кодовая страница сообщает компилятору, что при присвоении какого либо значения переменной типа AnsiString, он должен сначала конвертировать данные в эту статическую кодовую страницу (кроме случая, когда она CP_NONE, см. ниже раздел #RawByteString). Динамическая кодовая страница является характеристикой каждого отдельного экземпляра переменной типа AnsiString, такой же, как длина и счетчик ссылок - она определяет действительную кодовую страницу строковых данных, в текущий момент содержащихся в переменной типа AnsiString.

Статическая кодовая страница

Статическая кодовая страница для AnsiString может быть назначена единственным способом - объявлением нового типа:

pascal
type
  CP866String = type AnsiString(866); // обратите внимание на добавочное "type"

Статическая кодовая страница переменной, объявленной просто как AnsiString (без указания кодовой страницы) - будет CP_ACP. По сути, тип AnsiString теперь семантически определён в unit System следующим образом:

pascal
type
  AnsiString = type AnsiString(CP_ACP);

Еще одним предопределённым типом для AnsiString(X) в unit System является UTF8String:

pascal
type
  UTF8String = type AnsiString(CP_UTF8);

После того, как вы объявили собственный тип AnsiString(X), вы можете использовать его наименование для объявления переменных, параметров, полей и т. д. как обычно.

Обратите внимание, что CP_UTF16 и CP_UTF16BE не будут допустимыми кодовыми страницами для строк AnsiString. Результат объявления AnsiString с такими кодовыми страницами не определён.

Динамическая кодовая страница

Если строка со статической кодовой страницей X1 присваивается строке со статической кодовой страницей X2, и X1 не равно X2, строковые данные обычно сначала конвертируются в кодовую страницу X2 перед присвоением, и результирующая динамическая кодовая страница строки-результата будет X2. При присвоении строки переменной типа AnsiString с неуказанной кодовой страницей (= AnsiString(CP_ACP)) или ShortString, строковые данные будут конвертированы в DefaultSystemCodePage. Динамическая кодовая страница такой переменной типа AnsiString(CP_ACP) с момента присвоения поменяется на текущее значение DefaultSystemCodePage (например, 1251 для кодовой страницы Windows-1251), несмотря на то, что статическая кодовая страница такой переменной CP_ACP (Константа, обозначающая которую, не равна 1251). Это только один из примеров случая, когда статическая кодовая страница может отличаться от динамической кодовой страницы. В последующие разделах будет описано больше таких сценариев.

Внимание: как объяснялось выше, произойдет или нет возможное преобразование кодовых страниц, зависит только от значения статических кодовых страниц строк, участвующих в операции. Это значит, что если вы присваиваете одну переменную типа AnsiString(X) другой переменной типа AnsiString(X) и динамическая кодовая страница присваиваемой строки отличается от X, строковые данные не будут конвертированы в кодовую страницу X перед присваиванием.

RawByteString

Тип RawByteString объявлен следующим образом:

pascal
type
  RawByteString = type AnsiString(CP_NONE);

Как объяснено ранее, результат конверсии из/в кодовую страницу CP_NONE не определён. Поскольку нет смысла объявлять в RTL тип, поведение которого не определено, поведение строк типа RawByteString несколько отличается от других типов семейства AnsiString(X).

При первом приближении, RawByteString можно рассматривать как "нетипизированный AnsiString": присваивание переменной типа RawByteString строки типа AnsiString(X) будет выглядеть так же, как если переменной типа AnsiString(X) присвоить другую переменную AnsiString(X) с тем же самым значением X: никаких преобразований кодовых страниц или копирования не произойдёт, просто увеличится счетчик ссылок.

Не так бросается в глаза, что в случае, когда переменная типа RawByteString присваивается переменной типа AnsiString(X), случится следующее: никакого копирования или преобразования кодовых страниц не происходит, лишь увеличится счётчик ссылок. Обратите внимание, это означает, что результаты функций, возвращающих RawByteString никогда не преобразуются в статическую кодовую страницу переменной-назначения. Это - еще один пример случая, когда динамическая кодовая страница AnsiString(X) принимает значение, не совпадающее с её статической кодовой страницей.

Данный тип преимущественно используется, чтобы объявить const, constref и значения параметров, ожидающих любое значение типа AnsiString(X) без его преобразования в предопределённую статическую кодовую страницу. Обратите внимание, что если вы это сделаете, процедура, принимающая такие параметры, должна быть в состоянии обработать строки любой допустимой динамической кодовой страницы.

Параметры var и out также могут быть объявлены как RawByteString, но в этом случае компилятор выдаст ошибку, если на эти параметры будет отображена переменная типа AnsiString(X), чья статическая кодовая страница отличается от CP_NONE. Такое поведение согласуется в целом с трактовкой параметров var и out: они требуют полного совпадения типов переменных, передаваемых им. Вы можете заключить аргумент в декларацию явного приведения типов RawByteString() для того, чтобы воспрепятствовать появлению сообщения об ошибке, но при этом должны быть готовы к тому, что возвращаемая строка может иметь любую динамическую кодовую страницу.


Прим.перев.: Как можно так запутать такие простые вещи?! Весьма простое определение дается в небезызвестной статье на сайте Эмбаркадеро Delphi в мире Юникода, часть II: новые возможности RTL и классы для поддержки Юникода:"... в RTL введен тип RawByteString, который представляет собой строку, не связанную с какой-либо кодировкой... Назначение типа RawByteString – сделать возможной передачу строковых данных для любой кодовой страницы без каких-либо преобразований кодировки. Это очень полезно для подпрограмм, для которых кодировка не имеет значения, вроде побайтового поиска в строке."


Сцепление (конкантенации) строк

Как правило, в Паскале тип результата выражения не зависит от того, как этот результат используется впоследствии. Так, умножение двух чисел longint на 32-битной платформе и присваивание результата переменной типа int64 означает выполнение умножения с использованием 32-битной арифметики, и лишь конечный результат конвертируется в 64 бита.

Для строк с маркировкой кодовой страницы это правило не действует: сцепление двух или более строк всегда производится без каких либо потерь данных, и лишь после завершения операции строка-результат конвертируется в статическую кодовую страницу назначения (что может привести к потере данных).

Присвоение результата конкантенации переменной типа RawByteString производится особым образом:

Строковые константы

Строковые константы обрабатываются FPC следующим образом:

Во всех случаях, кроме последнего, действительная кодовая страница файла-исходника известна. Эта информация используется, когда компилятор вынужден конвертировать строковые константы в другую кодовую страницу. Для последнего же случая по умолчанию предполагается следующее: подразумевается, что строки принадлежат к кодовой странице 28591 (ISO 8859-1 Latin 1; Western European). Это предположение, или, в противоположность ему, фактическая кодовая страница и есть термин, обозначенный в тексте ниже как кодовая страница исходника.

Когда строковая константа присваивается переменной типа AnsiString(X) в программном коде или как часть типизированной константы или при инициализации переменной, тогда

Аналогично, если строковая константа присваивается переменной типа UnicodeString, она конвертируется во время компиляции из кодовой страницы исходника в UTF-16.

При присвоении переменным типов ShortString и PChar выполняются те же действия, что и для переменной типа AnsiString(CP_ACP).

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

Из сказанного выше следует, что для обеспечения предсказуемой интерпретации строковых констант в исходном коде, лучше всего включить явную директиву {$codepage xxx} (или использовать ее эквивалент в командной строке -Fc), или сохранять файлы с исходниками в UTF-8, с маркировкой BOM в начале файла.

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

Никаких изменений в индексацию строк не добавилось. Каждый элемент строки типа UnicodeString/WideString - это два байта, каждый элемент строки всех других строковых типов - один байт. Механизм индексации строк полностью игнорирует кодовые страницы и композиционные кодовые точки.

Изменения в RTL

Для того, чтобы полностью гарантировать целостность данных в присутствии строк, маркированных кодовой страницей, все процедуры в RTL и пакетах, допускающие параметры типа AnsiString, должны быть адаптированы. Причина в том, что если их параметры останутся просто типа AnsiString, то любая строка с отличающейся статической кодовой страницей будет сконвертирована в DefaultSystemCodePage при передаче в эти параметры. Это может привести к искажению и потере данных.

На настоящее время, основные процедуры, работающие с доступом к файловой системе, были обновлены с целью предотвращения искажения и потери данных. Ниже приведен полный список всех процедур, сохраняющих кодировку строк в FPC 2.7.1 и более поздних. Если иное не указано особо, все эти процедуры также имеют перегруженные (overload) версии, воспринимающие параметры типа 'UnicodeString''.

Что необходимо сделать с RTL

Поскольку приведённый выше перечень содержит все процедуры, подвергшиеся модификации, остальные процедуры RTL еще не поддерживают произвольные кодовые страницы. Этот раздел содержит перечень подводных камней, найденных отдельными людьми, и если это возможно, обходных путей. Обратите внимание, что процедуры, не попавшие ни в этот список, ни в список выше, использовать столь же опасно, как и перечисленные здесь.

TFormatSettings и DefaultFormatSettings

Тип аргументов ThousandSeparator и DecimalSeparator - AnsiChar. Это означает, что если переменная DefaultSystemCodePage имеет значение UTF-8 и разделитель текущей локали более одного байта длиной в этой кодировке, такие поля недостаточны для его представления. В качестве примера можно привести символы неразрывных пробелов, используемые во французской и русской локали для отображения ThousandSeparator.

Старые/устаревшие разделы

Warning: Эти разделы сохранены в исторических целях - пожалуйста обновите разделы выше с этой информацией, если она до сих пор используется. Начиная с FPC 2.7 (текущая разрабатываемая версия), расширенная поддержка Unicode была реализована.

Изменения, видимые пользователям

Полная поддержка строк, маркированных кодовой страницей невозможна без нарушения существующего кода. Ниже приведен список, который пытается обобщить наиболее важные изменения, видимые для пользователя.

См. также


From the Free Pascal wiki, page "FPC Unicode support/ru", as it stood on 2019-12-22 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revision 2, last changed by WikiArchive .

Talk about this page

Nobody has opened a bench for this page yet. Sign in to open one.