FPC Unicode support/ru
│
English (en) │
español (es) │
français (fr) │
русский (ru) │
Введение
До 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. Значение представляет собой соответствующую кодовую страницу, как это определено в Microsoft Windows. Дополнительно используются следующие три специальных значения кодовых страниц:
- CP_ACP: это значение представляет собой текущий набор "системной кодовой страницы по умолчанию". См. дополнительную информацию в разделе #Настройки кодовой страницы.
- CP_OEM: это значение представляет собой кодовую страницу OEM. На платформе Windows оно соответствует кодовой странице, используемой консолью (т.е. окном программы cmd.exe). На других платформах это значение интерпретируется точно так же, как CP_ACP.
- CP_NONE: это значение указывает, что никакая информация о кодовой странице не ассоциирована со строковыми данными. Результат любой прямой или косвенной операции по конвертированию этих данных в другую кодовую страницу не определён.
Примечание: идентификаторы кодовых страниц отличаются от имён кодовых страниц, используемых в директивах {$codepage xxx} (доступных и в текущем стабильном FPC). Имена кодовых страниц для директивы берутся из специального перечня, в котором представлены такие идентификаторы, как cp866, cp1251 и utf8.
Настройки кодовой страницы
Модуль system содержит глобальные переменные, указывающих кодовую страницу по умолчанию, использующуюся для некоторых операций.
DefaultSystemCodePage
- Назначение: определяет, как интерпретируется CP_ACP
- Начальное значение:
- Windows: результат системного вызова GetACP, который возвращает кодовую страницу Windows ANSI.
- iOS: UTF-8
- Unix (кроме iOS): Базируется на текущих установках переменных окружения LANG или LC_CTYPE. Обычно это UTF-8, но нет никаких гарантий этого.
- Другие платформы: CP_ACP (эти платформы на настоящий момент не поддерживают несколько кодовых страниц, и жестко привязаны к использованию своих, специфичных для операционной системы кодовых страниц во всех случаях)
- Модификация: Можно изменить значение переменной вызовом SetMultiByteConversionCodePage(CodePage: TSystemCodePage)
- Примечания: Поскольку значение этой переменной может быть изменено, не стоит полагаться на неё для определения действительной "системной кодовой страницы по умолчанию" операционной системы (даже в случае, если вы делаете это непосредственно после запуска программы, вы не можете быть уверены, что какие либо модули (unit) не внесли изменения в эту переменную при выполнении своих блоков инициализации).
DefaultFileSystemCodePage
- Назначение: определяет кодовую страницу, на которую переводятся имена файлов, каталогов и путей доступа перед вызовами API операционной системы, если RTL использует однобайтовый OS API для этих целей на текущей платформе. Эта кодовая страница также используется для промежуточных операций, касающих файловых путей внутри RTL перед тем, как сделать вызов OS API. Эта переменная отсутствует в Delphi и была введена в FPC для того, чтобы сделать возможным изменение значения DefaultSystemCodePage без разрушения интерфейсов RTL с вызовами функций API ОС, касающихся файловой системы.
- Начальное значение:
- Windows: UTF-8, поскольку RTL использует вызовы API операционной системы в кодировке UTF-16 (при этом данные не теряются в операциях промежуточного преобразования).
- macOS и iOS: UTF-8 (так определено Apple)
- Unix (исключая macOS и iOS): DefaultSystemCodePage, поскольку кодировка имён файлов не определена на платформах Unix (имена файлов являются нетипизированным байтовым массивом, который может быть интерпретирован любым способом; не гарантируется, что это правильный UTF-8)
- Другие платформы: то же самое, что в DefaultSystemCodePage
- Модификация: можно изменить это значение вызовом SetMultiByteFileSystemCodePage(CodePage: TSystemCodePage)
- Примечания: настройки Unix/macOS/iOS учитываются только в случае, когда установлен менеджер широких строк cwstring, иначе DefaultFileSystemCodePage после запуска программы будет иметь то же значение, что и DefaultSystemCodePage.
DefaultRTLFileSystemCodePage
- Назначение: определяет кодовую страницу, в кодировку которой транслируются имена файлов/путей перед тем, как они будут возвращены из процедур RTL, возвращающих строки формата RawByteString. Например, эта переменная используется для преобразования имён файлов/путей, возвращаемых RawbyteString-версиями функций SysUtils.FindFirst и System.GetDir. Основная причина их существования - обеспечение обратной совместимости с ранними версиями FPC, в которых всегда возвращались строки в кодировке, используемой для вызова функций API операционной системы, работающими с однобайтовой кодировкой (значение этой кодировки сейчас сохранено в DefaultSystemCodePage).
- Начальное значение
- Windows: DefaultSystemCodePage, для совместимости с предыдущими версиями.
- macOS и iOS: UTF-8, для совместимости с предыдущими версиями (на самом деле оно и раньше было UTF-8, результаты вызовов файлового API операционной системы не подвергались преобразованию).
- Unix (кроме macOS и iOS): DefaultSystemCodePage, по той же причине, что обозначена для DefaultFileSystemCodePage. Установка значения, отличающегося от DefaultFileSystemCodePage - очень плохая идея для этих платформ, любое преобразование кодовых страниц может разрушить строки, поскольку их начальная кодировка неизвестна.
- Модификация: можно изменить значение этой переменной вызовом SetMultiByteRTLFileSystemCodePage(CodePage: TSystemCodePage)
- Замечания: те же, что и для DefaultFileSystemCodePage.
Строки
Строковые/символьные типы
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
Эти типы остались такими же, как и в предыдущих версиях:
- Widestring - то же самое что и "COM BSTR" под Windows, и алиас для UnicodeString для всех других платформ. Строковые данные этого типа представлены с использованием UTF-16.
- UnicodeString - строка со счётчиком ссылок и максимальной длиной high(SizeInt) кодовых единиц UTF-16.
Ansistring
Семейство AnsiString - строковые типы со счетчиком ссылок и максимальной длиной high(SizeInt) байт. Кроме того, для каждой переменной такого типа сейчас сохраняется информация о кодовой странице, ассоциированной с данными.
Важнее всего понять, новое для переменной типа AnsiString в том, что она одновременно может обладать декларированной/статической/основной/по умолчанию кодовой страницей (далее по тексту статической кодовой страницей) и динамической кодовой страницей. Статическая кодовая страница сообщает компилятору, что при присвоении какого либо значения переменной типа AnsiString, он должен сначала конвертировать данные в эту статическую кодовую страницу (кроме случая, когда она CP_NONE, см. ниже раздел #RawByteString). Динамическая кодовая страница является характеристикой каждого отдельного экземпляра переменной типа AnsiString, такой же, как длина и счетчик ссылок - она определяет действительную кодовую страницу строковых данных, в текущий момент содержащихся в переменной типа AnsiString.
Статическая кодовая страница
Статическая кодовая страница для AnsiString может быть назначена единственным способом - объявлением нового типа:
type
CP866String = type AnsiString(866); // обратите внимание на добавочное "type"
Статическая кодовая страница переменной, объявленной просто как AnsiString (без указания кодовой страницы) - будет CP_ACP. По сути, тип AnsiString теперь семантически определён в unit System следующим образом:
type
AnsiString = type AnsiString(CP_ACP);
Еще одним предопределённым типом для AnsiString(X) в unit System является UTF8String:
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 объявлен следующим образом:
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 производится особым образом:
- если все строки, участвующие в операции, имели одинаковую динамическую кодовую страницу, результат также будет иметь эту кодовую страницу
- в иных случаях результат будет преобразован в CP_ACP (возможно, в будущем такое поведение изменится, так как оно выглядит не очень практичным)
Строковые константы
Строковые константы обрабатываются FPC следующим образом:
- Если в файле есть директива {$codepage xxx} (Например,
{$codepage UTF8}
), тогда строковые константы интерпретируются в соответствии с этой кодовой страницей, иначе: - Если файл начинается с маркировки UTF-8 BOM, тогда строковые константы интерпретируются в качестве строк UTF-8, иначе:
- строковые константы копируются без каких либо преобразований во внутренний буфер и интерпретируются в виде символов с использованием следующих кодовых страниц:
- DefaultSystemCodePage компьютера, на котором компилятор запущен, когда директива {$modeswitch systemcodepage} активна (то есть, компилирование одного и того же исходного кода на различных системах может приводить к тому, что строковые константы будут интерпретированы по разному; упомянутая директива введена для совместимости с Delphi и находится в состоянии «включено» по умолчанию в режиме {$mode delphiunicode})
- CP_ACP в случае, когда директива {$modeswitch systemcodepage} не активна (для совместимости с предыдущими версиями FPC)
Во всех случаях, кроме последнего, действительная кодовая страница файла-исходника известна. Эта информация используется, когда компилятор вынужден конвертировать строковые константы в другую кодовую страницу. Для последнего же случая по умолчанию предполагается следующее: подразумевается, что строки принадлежат к кодовой странице 28591 (ISO 8859-1 Latin 1; Western European). Это предположение, или, в противоположность ему, фактическая кодовая страница и есть термин, обозначенный в тексте ниже как кодовая страница исходника.
Когда строковая константа присваивается переменной типа AnsiString(X) в программном коде или как часть типизированной константы или при инициализации переменной, тогда
- если X = CP_NONE (т. е. результат - переменная типа RawByteString), результат будет таким же, как если бы строковая константа была назначена переменной типа AnsiString(CP_ACP)
- если X = CP_ACP и кодовая страница строковой константы отличается от CP_ACP, строковая константа конвертируется во время компиляции в кодовую страницу исходника. Если кодовая страница исходника также CP_ACP, строка будет сохранена в программе в неизменной форме с маркёром кодовой страницы CP_ACP, и следовательно, ее значение и интерпретация будут зависеть от действующего значения глобальной переменной DefaultSystemCodePage во время исполнения. Это обеспечивает совместимость со старыми версиями FPC при присвоении строковых констант переменным типа AnsiString без использования директивы {$codepage xxx} или маркировки UTF-8 BOM.
- при других значениях X строковая константа конвертируется во время компиляции в кодовую страницу 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.
- System: FExpand, LowerCase, UpperCase, GetDir, MKDir, ChDir, RMDir, Assign, Erase, Rename, процедуры стандартного ввода/вывода (Read/Write/Readln/Writeln/Readstr/Writestr), Insert, Copy, Delete, SetString
- ObjPas (автоматически подключается в режимах Delphi и ObjFPC): AssignFile
- SysUtils: FileCreate, FileOpen, FileExists, DirectoryExists, FileSetDate, FileGetAttr, FileSetAttr, DeleteFile, RenameFile, FileSearch, ExeSearch, FindFirst, FindNext, FindClose, FileIsReadOnly, GetCurrentDir, SetCurrentDir, ChangeFileExt, ExtractFilePath, ExtractFileDrive, ExtractFileName, ExtractFileExt, ExtractFileDir, ExtractShortPathName, ExpandFileName, ExpandFileNameCase, ExpandUNCFileName, ExtractRelativepath, IncludeTrailingPathDelimiter, IncludeTrailingBackslash, ExcludeTrailingBackslash, ExcludeTrailingPathDelimiter, IncludeLeadingPathDelimiter, ExcludeLeadingPathDelimiter, IsPathDelimiter, DoDirSeparators, SetDirSeparators, GetDirs, ConcatPaths, GetEnvironmentVariable
- Unix: процедуры fp*(), относящиеся к операциям с файловой системой (не имеют перегруженных экземпляров с параметрами типа UnicodeString), POpen
- DynLibs: все процедуры
Что необходимо сделать с RTL
Поскольку приведённый выше перечень содержит все процедуры, подвергшиеся модификации, остальные процедуры RTL еще не поддерживают произвольные кодовые страницы. Этот раздел содержит перечень подводных камней, найденных отдельными людьми, и если это возможно, обходных путей. Обратите внимание, что процедуры, не попавшие ни в этот список, ни в список выше, использовать столь же опасно, как и перечисленные здесь.
TFormatSettings и DefaultFormatSettings
Тип аргументов ThousandSeparator и DecimalSeparator - AnsiChar. Это означает, что если переменная DefaultSystemCodePage имеет значение UTF-8 и разделитель текущей локали более одного байта длиной в этой кодировке, такие поля недостаточны для его представления. В качестве примера можно привести символы неразрывных пробелов, используемые во французской и русской локали для отображения ThousandSeparator.
Старые/устаревшие разделы
Предупреждение: Эти разделы сохранены в исторических целях - пожалуйста обновите разделы выше с этой информацией, если она до сих пор используется. Начиная с FPC 2.7 (текущая разрабатываемая версия), расширенная поддержка Unicode была реализована.
Изменения, видимые пользователям
Полная поддержка строк, маркированных кодовой страницей невозможна без нарушения существующего кода. Ниже приведен список, который пытается обобщить наиболее важные изменения, видимые для пользователя.
- Заголовок строки получил два новых поля: кодировка и размер элемента. На 32-битных платформах это увеличило размер заголовка на 4 байта, на 64-битных платформах - на 8 байт.
- WideCharLenToString, UnicodeCharLenToString, WideCharToString, UnicodeCharToString и OleStrToString теперь возвращают UnicodeString вместо Ansistring, как было ранее.
- тип параметра dest функций WideCharLenToString и UnicodeCharLenToString изменён с Ansistring на Unicodestring
- UTF8ToAnsi и AnsiToUTF8 теперь принимают RawByteString