Difference between revisions of "Unicode Support in Lazarus/ru"

From Free Pascal wiki
Jump to navigationJump to search
m (Fixed syntax highlighting)
 
(25 intermediate revisions by 2 users not shown)
Line 14: Line 14:
  
 
По умолчанию RTL использует системную кодовую страницу для AnsiStrings (в таких операциях, как например FileExists и TStringList.LoadFromFile). Под Windows это не-Unicode кодировка, поэтому могут использоваться только символы из текущей языковой группы (не более 256 символов).
 
По умолчанию RTL использует системную кодовую страницу для AnsiStrings (в таких операциях, как например FileExists и TStringList.LoadFromFile). Под Windows это не-Unicode кодировка, поэтому могут использоваться только символы из текущей языковой группы (не более 256 символов).
LCL, с другой стороны, работает с кодировкой UTF-8, охватывающей весь диапазон Unicode. Под Linux и Mac OS X UTF-8 обычно является системной кодовой страницей, и здесь RTL использует по умолчанию CP_UTF8.
+
LCL, с другой стороны, работает с кодировкой UTF-8, охватывающей весь диапазон Unicode. Под Linux и macOS UTF-8 обычно является системной кодовой страницей, и здесь RTL использует по умолчанию CP_UTF8.
  
 
FPC, начиная с версии 3.0, обеспечивает API для изменения кодовой страницы RTL по умолчанию на другое значение. Lazarus (конкретно пакет LazUtils) использует данный API и изменяет кодовую страницу RTL по умолчанию на UTF-8 (CP_UTF8). Это означает, что пользователи Windows также могут теперь использовать строки UTF-8 в RTL.
 
FPC, начиная с версии 3.0, обеспечивает API для изменения кодовой страницы RTL по умолчанию на другое значение. Lazarus (конкретно пакет LazUtils) использует данный API и изменяет кодовую страницу RTL по умолчанию на UTF-8 (CP_UTF8). Это означает, что пользователи Windows также могут теперь использовать строки UTF-8 в RTL.
Line 115: Line 115:
 
= Код, который очень сильно зависит от кодовой страницы Windows =
 
= Код, который очень сильно зависит от кодовой страницы Windows =
  
There is a "plan B" for code that depends very much on Windows system codepage, or must write to Windows console beyond the console codepage.<br>
+
Существует «план B» для кода, который очень сильно зависит от системной кодовой страницы Windows или должен записываться в консоль Windows за пределами кодовой страницы консоли. <br>
See: [[Lazarus with FPC3.0 without UTF-8 mode]].<br>
+
См.: [[Lazarus with FPC3.0 without UTF-8 mode|Lazarus с FPC3.0 без режима UTF-8]].<br>
Fortunately it is not needed often. In most cases it is easier to convert data to UTF-8.
+
К счастью, это нужно нечасто. В большинстве случаев проще конвертировать данные в UTF-8.
  
== Writing to console ==
+
== Запись в консоль ==
  
Windows console output works '''if''' your characters belong to the console codepage.
+
Вывод консоли Windows работает корректно, '''если''' ваши символы принадлежат кодовой странице консоли.
For example a box-drawing characters '╩' in codepage CP437 is one byte '''#202''' and is often used like this:
+
Например, символ для рисования рамки <tt>'╩'</tt> в кодовой странице CP437 составляет один байт '''#202''' и часто используется так:
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
write('╩');
 
write('╩');
 
</syntaxhighlight>
 
</syntaxhighlight>
  
When you convert the code to UTF-8, for example by using Lazarus' source editor popup menu item '''File Settings / Encoding / UTF-8''' and clicking on the dialog button "Change file on disk", the ''╩'' becomes 3 bytes (#226#149#169), so the literal becomes a ''string''.
+
Когда вы конвертируете код в UTF-8, например, используя пункт всплывающего меню редактора исходного кода Lazarus '''File Settings (Параметры файла) / Encoding (Кодировка) / UTF-8''', и нажимая кнопку диалога "Change file on disk" (Изменить файл на диске), символ <tt>'╩'</tt> становится 3-х байтным (#226#149#169), поэтому литерал становится ''строкой''.
The procedures ''write'' and ''writeln'' convert the UTF-8 string to the current console codepage. So your console program now outputs the '╩' on Windows with any codepage (i.e. not only CP437) and it even works on Linux and Mac OS X. You can also use the '╩' in LCL strings, for instance Memo1.Lines.Add('╩');
 
  
If your characters do '''not''' belong to the console codepage, things get trickier. Then you cannot use the new Unicode system at all.<br>
+
Процедуры ''write'' и ''writeln'' преобразуют строку UTF-8 в текущую кодовую страницу консоли. Таким образом, ваша консольная программа теперь выводит <tt>'╩'</tt> в Windows с любой кодовой страницей (т.е. не только с CP437), и она даже работает в Linux и macOS. Вы также можете использовать <tt>'╩'</tt> в строках LCL, например, Memo1.Lines.Add(<tt>'╩'</tt>);
See:
 
[[Lazarus with FPC3.0 without UTF-8 mode#Problem System encoding and Console encoding .28Windows.29]]
 
  
= Unicode characters and codepoints in code =
+
Если ваши символы '''не''' принадлежат кодовой странице консоли, все усложняется. Тогда вы вообще не сможете использовать новую систему Unicode. <br>
 +
См.:
 +
[[Lazarus with FPC3.0 without UTF-8 mode#Problem System encoding and Console encoding .28Windows.29|Проблема Системной кодировки и Консольной кодировки (Windows)]]
  
See details for UTF-8 in [[UTF8 strings and characters]].
+
= Символы Юникода и кодовые точки в коде =
  
== CodePoint functions for encoding agnostic code ==
+
См. детали для UTF-8 в [[UTF8_strings_and_characters/ru|UTF8 strings and characters]].
  
LazUtils package has '''unit LazUnicode''' with special functions for dealing with codepoints, regardless of encoding.
+
== Функции кодовых точек для кодирования независимого кода ==
They use the UTF8...() functions from LazUTF8 when used in the UTF-8 mode, and UTF16...() functions from LazUTF16 when used in FPC's {$ModeSwitch UnicodeStrings} or in Delphi (yes, Delphi is supported!).
 
  
Currently the {$ModeSwitch UnicodeStrings} can be tested by defining "UseUTF16".
+
В пакете LazUtils есть '''модуль LazUnicode''' со специальными функциями для работы с кодовыми точками, независимо от кодировки.
There is also a test program LazUnicodeTest in components/lazutils/test directory. It has 2 build modes, UTF8 and UTF16, for easy testing. The test program also supports Delphi, then the UTF-16 mode is used obviously.
+
Они используют функции UTF8...() из LazUTF8 при использовании в режиме UTF-8, и функции UTF16...() из LazUTF16 при использовании в {$ModeSwitch UnicodeStrings} FPC или в Delphi (да, Delphi поддерживается!).
  
LazUnicode allows one source code to work between :
+
В настоящее время {$ModeSwitch UnicodeStrings} можно протестировать, задав "UseUTF16".
* Lazarus with its UTF-8 solution.
+
Также есть тестовая программа LazUnicodeTest в каталоге components/lazutils/test. Она имеет 2 режима сборки, UTF8 и UTF16, для удобства тестирования. Тестовая программа также поддерживает Delphi, тогда, очевидно, используется режим UTF-16.
* Future FPC and Lazarus with Delphi compatible UTF-16 solution.
 
* Delphi, where String = UnicodeString.
 
  
It provides these encoding agnostic functions:
+
LazUnicode позволяет одному исходному коду работать между:
 +
* Lazarus с его UTF-8 решением.
 +
* Будущие FPC и Lazarus с Delphi-совместимым решением UTF-16.
 +
* Delphi, где String = UnicodeString.
  
* CodePointCopy() - Like UTF8Copy()
+
Это обеспечивается перечисленными ниже функциями, не зависящим от кодирования:
* CodePointLength() - Like UTF8Length()
 
* CodePointPos() - Like UTF8Pos()
 
* CodePointSize() - Like UTF8CharacterLength() (<b>Deprecated</b>. Use UTF8CodepointSize instead. [https://lazarus-ccr.sourceforge.io/docs/lazutils/lazutf8/utf8characterlength.html See more])
 
* UnicodeToWinCP() - Like UTF8ToWinCP()
 
* WinCPToUnicode() - Like WinCPToUTF8()
 
  
It also provides an enumerator for CodePoints which the compiler uses for its for-in loop.
+
* <tt>CodePointCopy()</tt> - аналогична <tt>UTF8Copy()</tt>
As a result, regardless of encoding, this code works:
+
* <tt>CodePointLength()</tt> - аналогична <tt>UTF8Length()</tt>
<syntaxhighlight>
+
* <tt>CodePointPos()</tt> - аналогична <tt>UTF8Pos()</tt>
 +
* <tt>CodePointSize()</tt> - аналогична <tt>UTF8CharacterLength()</tt> (функция <b>устарела</b>. Вместо нее используйте <tt>UTF8CodepointSize</tt>. [https://lazarus-ccr.sourceforge.io/docs/lazutils/lazutf8/utf8characterlength.html См. подробнее])
 +
* <tt>UnicodeToWinCP()</tt> - аналогична <tt>UTF8ToWinCP()</tt>
 +
* <tt>WinCPToUnicode()</tt> - аналогична <tt>WinCPToUTF8()</tt>
 +
 
 +
Этот режим также предоставляет перечислитель для кодовых точек, который компилятор использует для цикла for-in.
 +
В результате, независимо от кодировки, этот код работает:
 +
<syntaxhighlight lang=pascal>
 
  var s, ch: String;
 
  var s, ch: String;
 
  ...
 
  ...
Line 169: Line 170:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Delphi does not provide similar functions for CodePoints for its UTF-16 solution.
+
Delphi не предоставляет аналогичные функции для CodePoints в своем решении UTF-16.
Practically most Delphi code treats UTF-16 as a fixed-width encoding which has lead to lots of broken UTF-16 code out there.
+
Практически большая часть кода Delphi рассматривает UTF-16 как кодировку с фиксированной шириной, что привело к возникновению большого количества неработающего кода UTF-16.
It means using LazUnicode also for Delphi will improve code quality!
+
Это означает, что использование LazUnicode также для Delphi улучшит качество кода!
  
Both units LazUnicode and LazUTF16 are needed for Delphi usage.
+
Для использования Delphi  необходимы оба модуля LazUnicode и LazUTF16.
  
= String Literals =
+
= Строковые литералы =
  
Sources should be saved in UTF-8 encoding. Lazarus creates such files by default.
+
Исходный код должен сохраняться в кодировке UTF-8. Lazarus создает такие файлы по умолчанию.
You can change the encoding of imported files via right click in source editor / File Settings / Encoding.
+
Вы можете изменять кодировку импортированных файлов, щелкая правой кнопкой мыши в редакторе исходного кода / File Settings (Настройки файла) / Encoding (Кодировка).
  
Usually {$codepage utf8} / -FcUTF8 is not needed. This is rather counter-intuitive because the meaning of that flag is to treat string literals as UTF-8. However the new UTF-8 mode switches the encoding at run-time, yet constants are evaluated at compile-time.
+
Обычно директива <code>{$codepage utf8} / -FcUTF8</code> не требуется. Это довольно нелогично, поскольку значение этого флага заключается в обработке строковых литералов как UTF-8. Однако новый режим UTF-8 переключает кодирование во время выполнения, а константы оцениваются во время компиляции.
  
So, without -FcUTF8 the compiler (wrongly) thinks the constant string is encoded with system code page. Then it sees a String variable with default encoding (which will be changed to UTF-8 at run-time but the compiler does not know it). Thus, same default encodings, no conversion needed, the compiler happily copies the characters and everything goes right, while actually it was fooled twice during the process.
+
Таким образом, без <code>-FcUTF8</code> компилятор (ошибочно) считает, что строковая константа кодируется системной кодовой страницей. Затем он видит переменную String с кодировкой по умолчанию (которая будет изменена на UTF-8 во время выполнения, но компилятор этого не знает). Таким образом, то же для кодировки по умолчанию, преобразование не требуется, компилятор с радостью копирует символы, и все идет хорошо, в то время как на самом деле его дважды обманывали в течение процесса.
  
'''Example:'''
+
'''Пример:'''
  
As a rule of thumb, use "String" type and assigning literals works.
+
По правилу шаловливых ручек, используйте тип «String» и заставляйте литералы работать.
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
const s1 = 'äй';
 
const s1 = 'äй';
 
const s2: string = #$C3#$A4; // ä
 
const s2: string = #$C3#$A4; // ä
Line 195: Line 196:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Note: UTF-8 string can be composed from numbers, as done for s2.
+
{{Note| Строка UTF-8 может состоять из чисел, как это продемонстрировано для s2.}}
 +
 
 +
*Литералы AnsiString/String работают как с директивой {$codepage utf8} / -FcUTF8 , так и без неё.
 +
<syntaxhighlight lang=pascal>
 +
const s: string = 'äй';
 +
</syntaxhighlight>
 +
 
 +
*Литералы ShortString работоспособны только '''без''' указания директивы {$codepage utf8} / -FcUTF8. Вы можете сделать следующее:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit unit1;
 +
{$Mode ObjFPC}{$H+}
 +
{$modeswitch systemcodepage} // прекращает действие директивы -FcUTF8
 +
interface
 +
const s: string[15] = 'äй';
 +
end.
 +
</syntaxhighlight>
 +
 
 +
В качестве альтернативы, возможно использовать строки shortstring с включенной директивой $codepage путем прямого присвоения кодов символов:
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit unit1;
 +
{$Mode ObjFPC}{$H+}
 +
{$codepage utf8}
 +
interface
 +
const s: String[15] = #$C3#$A4; // ä
 +
end.
 +
</syntaxhighlight>
 +
 
 +
*WideString/UnicodeString/UTF8String работают только '''с''' {$codepage utf8} / -FcUTF8.
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit unit1;
 +
{$Mode ObjFPC}{$H+}
 +
{$codepage utf8}
 +
interface
 +
const ws: WideString = 'äй';
 +
end.
 +
</syntaxhighlight>
  
Assigning a string literal to other string types than plain "String" is more tricky.
+
Присвоение строкового литерала другим строковым типам, кроме простого «String», более мудрено.
See the tables for what works and what doesn't.
+
Смотрите таблицы, что работает, а что нет.
  
== Assign string literals to different string types ==
+
== Присвоение строковых литералов различным типам строк ==
Here ''working'' means correct codepage and correct codepoints. Codepage 0 or codepage 65001 are both correct, they mean UTF-8.<br>
+
Здесь ''работает'' означает правильную кодовую страницу и правильные кодовые точки. Кодовая страница 0 или кодовая страница 65001 - обе являются правильными, они означают UTF-8. <br>
  
=== Without {$codepage utf8} or compilerswitch -FcUTF8 ===
+
=== Без {$codepage utf8} или переключателя компилятора -FcUTF8 ===
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
! String Type, UTF-8 Source !! Example !! Const (in Source) !! Assigned to String !! Assigned to UTF8String !! Assigned to UnicodeString !! Assigned to CP1252String !! Assigned to RawByteString !! Assigned to ShortString !! Assigned to PChar
+
! Тип String, исходник в UTF-8 !! Пример !! Const (в исходнике) !! Присвоение String !! Присвоение UTF8String !! Присвоение UnicodeString !! Присвоение CP1252String !! Присвоение RawByteString !! Присвоение  ShortString !! Присвоение PChar
 
|----
 
|----
 
|const||const s = 'äöü';||class="working"| working ||class="working"| working ||class="not"| wrong || class="not"| wrong || class="not"| wrong ||class="working"| working ||class="working"| working ||class="working"| working
 
|const||const s = 'äöü';||class="working"| working ||class="working"| working ||class="not"| wrong || class="not"| wrong || class="not"| wrong ||class="working"| working ||class="working"| working ||class="working"| working
Line 217: Line 256:
 
|UnicodeString||const s: UnicodeString = 'äöü';||class="not"| wrong ||class="not"| wrong ||class="not"| wrong || class="not"| wrong || class="not"| wrong ||class="not"| wrong ||class="not"| wrong ||class="not"| wrong
 
|UnicodeString||const s: UnicodeString = 'äöü';||class="not"| wrong ||class="not"| wrong ||class="not"| wrong || class="not"| wrong || class="not"| wrong ||class="not"| wrong ||class="not"| wrong ||class="not"| wrong
 
|----
 
|----
|String with declared code page||type CP1252String = type AnsiString(1252);||class="not"| wrong ||class="not"| wrong ||class="not"| wrong || class="not"| wrong || class="not"| wrong||class="not"| wrong ||class="not"| wrong ||class="not"| wrong
+
|String с объявленной кодовой страницей||type CP1252String = type AnsiString(1252);||class="not"| wrong ||class="not"| wrong ||class="not"| wrong || class="not"| wrong || class="not"| wrong||class="not"| wrong ||class="not"| wrong ||class="not"| wrong
 
|----
 
|----
 
|RawbyteString||const s: RawbyteString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="partial"|to codepage 0 changed ||class="working"| working ||class="working"| working ||class="working"| working
 
|RawbyteString||const s: RawbyteString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="partial"|to codepage 0 changed ||class="working"| working ||class="working"| working ||class="working"| working
Line 223: Line 262:
 
|PChar||const c: PChar = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="not"| wrong ||class="working"| working ||class="working"| working ||class="working"| working
 
|PChar||const c: PChar = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="not"| wrong ||class="working"| working ||class="working"| working ||class="working"| working
 
|}
 
|}
=== With {$codepage utf8} or compilerswitch -FcUTF8 ===
+
 
 +
=== С {$codepage utf8} или переключателем компилятора -FcUTF8 ===
 
{| class="wikitable sortable"
 
{| class="wikitable sortable"
! String Type, UTF-8 Source !! Example !! Const (in Source) !! Assigned to String !! Assigned to UTF8String !! Assigned to UnicodeString !! Assigned to CP1252String !! Assigned to RawByteString !! Assigned to ShortString !! Assigned to PChar
+
! Тип String, исходник в UTF-8 !! Пример !! Const (в исходнике) !! Присвоение String !! Присвоение UTF8String !! Присвоение UnicodeString !! Присвоение CP1252String !! Присвоение RawByteString !! Присвоение ShortString !! Присвоение PChar
 
|----
 
|----
 
|const||const s = 'äöü';||class="partial"|UTF-16 encoded ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="working"| working ||class="working"| working
 
|const||const s = 'äöü';||class="partial"|UTF-16 encoded ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="working"| working ||class="working"| working
Line 237: Line 277:
 
|UnicodeString||const s: UnicodeString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="working"| working ||class="not"| wrong
 
|UnicodeString||const s: UnicodeString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="working"| working ||class="not"| wrong
 
|----
 
|----
|String with declared code page||type CP1252String = type AnsiString(1252);||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="not"| wrong ||class="not"| wrong
+
|String с объявленной кодовой страницей||type CP1252String = type AnsiString(1252);||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="working"| working ||class="working"| working ||class="not"| wrong ||class="not"| wrong
 
|----
 
|----
 
|RawbyteString||const s: RawbyteString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="partial"|to codepage 0 changed  ||class="working"| working ||class="working"| working ||class="working"| working
 
|RawbyteString||const s: RawbyteString = 'äöü';||class="working"| working ||class="working"| working ||class="working"| working || class="working"| working || class="partial"|to codepage 0 changed  ||class="working"| working ||class="working"| working ||class="working"| working
Line 244: Line 284:
 
|}
 
|}
  
Remember, assignment between variables of different string types always works thanks to their dynamic encoding in FPC 3+.
+
Помните, что присваивание между переменными разных типов строк всегда работает благодаря их динамическому кодированию в FPC 3+.
The data is converted automatically when needed.<br>
+
Данные конвертируются автоматически при необходимости. <br>
Only string literals are a challenge.
+
Только строковые литералы являются проблемой.
  
= Coming from older Lazarus + LCL versions =
+
= Переход из более старых версий Lazarus + LCL =
  
Earlier (before 1.6.0) LCL supported Unicode with dedicated UTF8 functions. The code was not at all compatible with Delphi.
+
Ранее (до 1.6.0) LCL поддерживал Unicode с выделенными функциями UTF8. Код не был совместим с Delphi.
  
Now many old LCL applications continue to work without changes. However it makes sense to clean the code to make it simpler and more Delphi compatible.
+
Сейчас многие старые приложения LCL продолжают работать без изменений. Однако имеет смысл очистить код, чтобы сделать его более простым и более совместимым с Delphi.
Code that reads/writes data with Windows system codepage encoding breaks and must be changed. (See [[#Reading / writing text file with Windows codepage]]).
+
Код, который читает/записывает данные с использованием кодировки системной кодовой страницы Windows, нарушается и должен быть изменен. (См. [[Unicode_Support_in_Lazarus/ru#.D0.A7.D1.82.D0.B5.D0.BD.D0.B8.D0.B5_.2F_.D0.B7.D0.B0.D0.BF.D0.B8.D1.81.D1.8C_.D1.82.D0.B5.D0.BA.D1.81.D1.82.D0.BE.D0.B2.D0.BE.D0.B3.D0.BE_.D1.84.D0.B0.D0.B9.D0.BB.D0.B0_.D1.81_.D0.BA.D0.BE.D0.B4.D0.BE.D0.B2.D0.BE.D0.B9_.D1.81.D1.82.D1.80.D0.B0.D0.BD.D0.B8.D1.86.D0.B5.D0.B9_Windows|Чтение / запись текстового файла с кодовой страницей Windows]]).
  
Explicit conversion functions are only needed for I/O with Windows codepage data or when calling Windows Ansi functions. Otherwise FPC takes care of converting encodings automatically. Empty conversion functions are provided to make your old code compile.
+
Явные функции преобразования необходимы только для ввода-вывода с данными кодовой страницы Windows или при вызове функций Windows Ansi. В противном случае FPC позаботится о автоматическом преобразовании кодировок. Для преобразования вашего старого кода предусмотрены пустые функции преобразования.
* UTF8Decode, UTF8Encode - Almost all can be removed.
+
* UTF8Decode, UTF8Encode - Почти все можно удалить.
* UTF8ToAnsi, AnsiToUTF8 - Almost all can be removed.
+
* UTF8ToAnsi, AnsiToUTF8 - Почти все можно удалить.
* UTF8ToSys, SysToUTF8 - All can be removed. They are now dummy no-ops and only return their parameter.
+
* UTF8ToSys, SysToUTF8 - Почти все можно удалить. Теперь они являются пустышками и возвращают только свои параметры.
  
File functions in RTL now take care of file name encoding.
+
Файловые функции в RTL теперь заботятся о кодировке имен файлов.
All (?) file name related ...UTF8() functions can be replaced with the Delphi compatible function without UTF8 suffix.
+
Все (?) связанные с именем файла функции ...UTF8() можно заменить на Delphi-совместимую функцию без суффикса UTF8.
For example FileExistsUTF8 can be replaced with FileExists.
+
Например, FileExistsUTF8 можно заменить на FileExists.
  
Most UTF8...() string functions can be replaced with the Delphi compatible Ansi...() functions. For example UTF8UpperCase() -> AnsiUpperCase().
+
Большинство строковых функций UTF8...() можно заменить на совместимые с Delphi функции Ansi...(). Например, UTF8UpperCase() -> AnsiUpperCase().
  
Now Unicode works in non-GUI programs, too. It only requires a dependency for LazUtils and placing LazUTF8 unit into the uses section of main program file.
+
Теперь Unicode работает и в программах без GUI. Требуется только зависимость от LazUtils и размещение модуля LazUTF8 в разделе uses основного файла программы.
  
For historical reference, this was the old Unicode support in LCL:
+
Для исторической справки, это была старая поддержка Unicode в LCL:
 
[[Old LCL Unicode Support]]
 
[[Old LCL Unicode Support]]
  
= Technical implementation =
+
= Техническая реализация =
  
What actually happens in the Unicode system? These 2 FPC functions are called in an early initialization section, setting the default String encoding in FPC to UTF-8 :
+
Что на самом деле происходит в системе Unicode? Эти 2 функции FPC вызываются в разделе ранней инициализации, устанавливая кодировку String по умолчанию в FPC в UTF-8:
 
   SetMultiByteConversionCodePage(CP_UTF8);
 
   SetMultiByteConversionCodePage(CP_UTF8);
 
   SetMultiByteRTLFileSystemCodePage(CP_UTF8);
 
   SetMultiByteRTLFileSystemCodePage(CP_UTF8);
  
Under Windows the UTF8...() functions in LazUTF8 (LazUtils) are set as backends for RTL's Ansi...() string functions. Thus those functions work in a Delphi compatible way.
+
В Windows функции UTF8...() в LazUTF8 (LazUtils) устанавливаются в качестве бэкэндов для строковых функций RTL Ansi...(). Таким образом, эти функции работают совместимым с Delphi способом.
 
 
= Open issues =
 
 
 
* '''TFormatSettings''' char (bug [http://bugs.freepascal.org/view.php?id=27086 27086]): for example: '''ThousandSeparator''', '''DecimalSeparator''', '''DateSeparator''', '''TimeSeparator''', '''ListSeparator'''. These should be replaced with string to support UTF-8. For example under Linux with LC_NUMERIC=ru_RU.utf8 the thousand separator is the two byte nbsp/160.
 
** '''Workaround:''' use single space characters instead as done in patch here: [http://bugs.freepascal.org/view.php?id=27099 27099]
 
 
 
=== WinAPI function calls in FPC libs ===
 
 
 
* Unit registry, TRegistry - this unit uses Windows Ansi functions and therefore you need to use UTF8ToWinCP, WinCPToUTF8. Formerly it needed UTF8ToSys.
 
* All Windows API Ansi function calls in FPC's libraries must be replaced with the "W" function version. This must be done in any case for the future UTF-16 support, thus there is no conflict of interest here.
 
* '''TProcess''' - under Windows TProcess FPC 3.0 only supports system codepage. Use either TProcessUTF8 of unit '''utf8process''' or use FPC trunk where it is fixed, see issue [http://bugs.freepascal.org/view.php?id=29136 29136]
 
 
 
ToDo: List all related FPC bug tracker issues and patches that should be applied.
 
 
 
= Future =
 
 
 
The goal of FPC project is to create a Delphi compatible UnicodeString (UTF-16) based solution, but it is not ready yet. It may take some time to be ready.
 
 
 
This UTF-8 solution of LCL in its current form can be considered temporary.
 
In the future, when FPC supports UnicodeString fully in RTL and FCL, Lazarus project will provide a solution for LCL that uses it.
 
At the same time the goal is to preserve UTF-8 support although it may require changes to string types or something. Nobody know the details yet. We will tell when we know...
 
 
 
In essence LCL will probably have 2 versions, one for UTF-8 and one for UTF-16.
 
 
 
= FAQ =
 
 
 
==What about Mode DelphiUnicode?==
 
 
 
The '''{$mode delphiunicode}''' was added in FPC 2.7.1 and is like ''{$Mode Delphi}'' with ''{$ModeSwitch UnicodeStrings}''. See the next question about ''ModeSwitch UnicodeStrings''.
 
 
 
==What about ModeSwitch UnicodeStrings?==
 
 
 
The '''{$ModeSwitch UnicodeStrings}''' was added in FPC 2.7.1 and defines "String" as "UnicodeString" (UTF-16), "Char" as "WideChar", "PChar" as "PWideChar" and so forth. This affects only the current unit. Other units including those used by this unit have their own "String" definition. Many RTL strings and types (e.g. TStringList) uses 8-bit strings, which require conversions from/to UnicodeString, which are added automatically by the compiler. The LCL uses UTF-8 strings.  It is recommended to use UTF-8 sources with or without "-FcUTF8".
 
 
 
==Why not use UTF8String in Lazarus?==
 
 
 
Short answer: Because the FCL does not use it.
 
 
 
Long answer:
 
'''UTF8String''' is defined in the '''system''' unit as
 
<syntaxhighlight>UTF8String = type AnsiString(CP_UTF8);</syntaxhighlight>
 
 
 
The compiler always assumes it has UTF-8 encoding (CP_UTF8), which is a multi byte encoding (i.e. 1-4 bytes per codepoint). Note that the [] operator accesses bytes, not characters nor codepoints. Same for UnicodeString, but words instead of bytes.
 
On the other hand a '''String''' is assumed at compile time to have ''DefaultSystemCodePage'' (CP_ACP). ''DefaultSystemCodePage'' is defined at run time, so the compiler conservatively assumes that ''String'' and ''UTF8String'' have different encodings. When you assign or combine ''String'' and ''UTF8String'' the compiler inserts conversion code. Same for ''ShortString'' and ''UTF8String''.
 
 
 
Lazarus uses the FCL, which uses ''String'', so using ''UTF8String'' would add conversions. If ''DefaultSystemCodePage'' is not UTF-8 you lose characters. If it is UTF-8 then there is no point to use ''UTF8String''.
 
 
 
''UTF8String'' becomes useful when eventually there is an UTF-16 FCL.
 
 
 
==Why does UTF8String show strange characters and String works==
 
 
 
For example:
 
<syntaxhighlight>
 
var s: UTF8String = 'ä';  // with default flags (i.e. no -Fc) this creates garbage
 
              // even on a UTF-8 Linux system
 
</syntaxhighlight>
 
 
 
Question: Is it a bug? Answer: No, because it works as documented.
 
 
 
FPC ignores the LANG variable to create on every system the same result. For historical reasons/Delphi compatibility it uses ISO-8859-1 as default. Lazarus prefers UTF-8.
 
*UTF-8 sources work with ''String'', because
 
*# FPC does not add conversion code for normal String literals by default.
 
*# The ''source codepage'' is equal to the ''runtime codepage''. On Windows LazUTF8 sets it to CP_UTF8.
 
*UTF8String requires UTF-8 sources. Since FPC 3.0 for UTF8String you must tell the compiler, that the source is UTF8 (''-FcUTF8'', ''{$codepage UTF8}'', or save file as UTF-8 with BOM).
 
**Note: If you tell the compiler the source encoding UTF-8 it changes all non ASCII string literals of this unit to UTF-16, increasing the size of the binary, adding some overhead and PChar on literals require an explicit conversion. That's why Lazarus does not add it by default.
 
 
 
==What happens when I use $codepage utf8?==
 
 
 
FPC has very limited UTF-8 support. In fact, FPC only supports storing literals as either "default" encoded 8-bit strings or widestrings. So any non default codepage is converted to widestring, even if it is the system codepage. For example most Linux/Mac/BSD use UTF-8 as system codepage. Passing -Fcutf8 to the compiler will store the string literal as widestring.
 
 
 
At run time the widestring literal is converted. When you assign the literal to an AnsiString the widestring literal is converted using the widestringmanager to the system encoding. The default widestringmanager under Unix simply converts the widechars to chars, destroying any non ASCII character. You must use a widestringmanager like the unit cwstring to get correct conversion. Unit LazUTF8 does that.
 
 
 
= See Also =
 
 
 
Information about Unicode, codepages, string types and RTL in FPC.<br>
 
Note, that information is not entirely usefull with Lazarus UTF-8 system, because from FPC's point of view it is a hack and changes the default codepage.<br>
 
[[FPC Unicode support]]
 
 
 
==================================
 
==================================
 
==================================
 
 
 
 
 
= Совместимость с Unicode Delphi =
 
 
 
Для консольных программ, unit LazUTF8 должен присутствовать в секции uses главного файла программы. В Delphi такого unit'а нет.
 
 
 
== RTL функции ASCII диапазона ==
 
 
 
RTL функции, работающие в ASCII диапазоне (такие как UpperCase) совместимы, но работают быстрее в UTF-8 RTL.
 
В Delphi все строковые функции замедлены, после того как были переключены на UTF-16.
 
 
 
== RTL функции Ansi...() Unicode ==
 
 
 
RTL фунции Ansi...() , работающие с кодовыми страницами / Unicode (такие как AnsiUpperCase), совместимы.
 
 
 
== Чтение индивидуальных кодовых точек ==
 
 
 
Не совместимо, хотя довольно просто создать код, работающий с обеими кодировками.
 
 
 
В Delphi есть такие функции, как NextCharIndex, IsHighSurrogate, IsLowSurrogate, назначением которых является работа с суррогатными парами UTF-16, кодовыми точками, состоящими из двух UnicodeChar(*) (WideChar, Word, 2-байтовые).
 
Однако, эти функции не упоминаются ни в одном примере кода в большинстве учебников. Большинство учебников заявляют, что функция Copy() работает так же, как было в версиях Delphi, более ранних чем D2009. Однако кодовая точка теперь может состоять из двух UnicodeChar(*) и Copy() может возвратить половину её.
 
 
 
UTF-8 в этом имеет преимущества. Поскольку мультибайтовые кодовые точки здесь встречаются постоянно, код, работающий со строками UTF-8 обычно корректен.
 
 
 
См. раздел ниже: '''Работа со строками и символами UTF8 в коде''' насчет примеров использования UTF-8 и того, как создавать код, правильно работающий с обеими кодировками.
 
 
 
(*)
 
* Наименования "UnicodeString" и "UnicodeChar" для типов UTF-16 были весьма неудачным выбором со стороны Borland.
 
* Кодовая точка Unicode - "реальное" определение символа в Unicode, которое может быть закодировано разными способами, и ее длина зависит от кодировки.
 
* Символ Unicode может состоять как из одной кодовой точки, так и являться декомпозиционным символом нескольких кодовых точек. Да, это сложно...
 
 
 
= Совместимость с LCL в Lazarus 1.x =
 
 
 
Большинство приложений Lazarus LCL продолжат работать без изменений. Однако, поддержка Unicode становится проще, что является поводом подчистить код.
 
Код, читающий из потоков или записывающий данные в потоки, файлы или базы данных в кодировках, не являющихся UTF-8, будет работать некорректно и потребует изменений. (Ниже см. примеры).
 
Явное использование функций преобразования необходимо только при вызове Ansi-версий функций Windows API. Во всех других случаях FPC позаботится об автоматическом преобразовании кодировок. Функции-заглушки, тем не менее, предоставлены для возможности откомпилировать старый код.
 
* UTF8Decode, UTF8Encode — В большинстве случаев все вызовы можно удалить.
 
* UTF8ToSys, SysToUTF8, UTF8ToAnsi, AnsiToUTF8 -  — В большинстве случаев все вызовы можно удалить.
 
 
 
Файловые функции в RTL теперь самостоятельно заботятся о кодировке имён файлов.
 
Все (?) функции, относящиеся к именам файлов, содержащие в названии ...UTF8() могут быть заменены совместимыми с Delphi функциями без суффикса UTF8.
 
Например, FileExistsUTF8 может быть заменено на FileExists.
 
 
 
Большинство строковых функций UTF8...() могут быть заменены на совместимые с Delphi функции Ansi...().
 
Функции UTF8...() из LazUTF8 регистрируются в качестве функций обратного вызова для функций Ansi...() из SysUtils.
 
 
 
UTF-8 работает также в программах без графического интерфейса. Для этого требуется зависимость от LazUtils и размещение юнита LazUTF8 в секции uses головного файла программы.
 
 
 
== Чтение текстового файла со строками в кодовой странице Windows ==
 
 
 
Методика несовместима с прежним кодом Lazarus. Практически, вы должны вставить код, работающий с системной кодовой страницей и конвертировать данные в UTF-8 сразу, как только представится такая возможность.
 
 
 
Можно установить правильную кодовую страницу для строки следующим образом:
 
  var
 
    StrIn, StrOut: String;
 
  ...
 
  SetCodePage(RawByteString(StrIn), 1251, false);  // 1251 для русской Windows !! (или Windows.GetACP())
 
  ...
 
 
 
или использовать RawByteString и делать явное преобразование:
 
  uses ... , LConvEncoding;
 
  ...
 
  var
 
    StrIn: RawByteString;
 
    StrOut: String;
 
  ...
 
  StrOut := CP1252ToUTF8(StrIn,true);
 
 
 
ToDo ...
 
 
 
== Код, в сильнейшей степени зависящий от кодовой страницы Windows ==
 
 
 
Код некоторых программ так тесно зависит от сиcтемной кодовой страницы, что использование нового режима RTL UTF-8 выглядит непрактичным.
 
Здесь есть два выбора :
 
* Продолжить использование Lazarus с FPC 2.6.4. Это хорошее решение для кода, находящегося в режиме поддержки. Lazarus пока способен компилироваться FPC 2.6.4 и старые функции UTF8...() на месте.
 
* Использовать FPC 3.0 при отключенном новом режиме UTF-8, путем определения директивы DisableUTF8RTL. Это может повлечь некоторые весьма нехорошие проблемы, о которых написано здесь: [[Lazarus with FPC3.0 without UTF-8 mode]].
 
 
 
= Функции работы с кодовыми точками =
 
 
 
В LazUtils появятся специальные функции для операций с кодовыми точками. Они будут не только использовать старые функции UTF8...() из LCL, но и будут иметь алиасы на функции, дающие возможность использовать кодировки, обычные как для Delphi, так и для FPC {$mode DelphiUnicode}.
 
 
 
* CodePointCopy() - похоже на UTF8Copy()
 
* CodePointLength() -  похоже на  UTF8Length()
 
* CodePointPos() -  похоже на  UTF8Pos()
 
* CodePointToWinCP()
 
* WinCPToCodePoint()
 
* CodePointByteCount() -  похоже на UTF8CharacterLength()
 
 
 
Интересен вопрос, как CodePointCopy, CodePointLength и CodePointPos должны быть реализованы в Delphi, который не обеспечивает таких функции для UTF-16. (Или всё таки обеспечивает?)
 
Практически весь код, написанный на Delphi использует обычные Copy, Length и Pos вместо функций, корректно работающих с кодовыми точками.
 
 
 
= Работа со строками и символами UTF8 в коде =
 
 
 
Детали см. в [[UTF8_strings_and_characters]].
 
 
 
= Строковые литералы =
 
 
 
Исходники должны быть записаны в кодировке UTF-8. Lazarus создаёт такие файлы по умолчанию. Вы можете изменить кодировку импортируемых файлов кликом правой кнопкой мыши в окне редактора исходного кода и последовательным выбором пунктов всплывающего меню File Settings (Настройки файла) / Encoding (Кодировка).
 
 
 
Директива компиляции {$codepage utf8} / -FcUTF8 в большинстве случаев не требуется.
 
Это выглядит нелогичным, потому что сам смысл этого флага в правильной интерпретации литералов, введенных в текст исходника в кодировке UTF-8. Однако, новый режим работы RTL во время исполнения установит UTF-8 кодировкой по умолчанию, и все строковые константы, не имеющие маркировки кодовой страницы, при соответствующих присвоениях будут проинтерпретированы правильно.
 
 
 
'''Примеры:'''
 
 
 
*Литералы AnsiString/String работают как с директивой {$codepage utf8} / -FcUTF8 , так и без неё.
 
<syntaxhighlight>
 
const s: string = 'äй';
 
</syntaxhighlight>
 
 
 
*Литералы ShortString работоспособны только '''без''' указания директивы {$codepage utf8} / -FcUTF8. Вы можете сделать следующее:
 
 
 
<syntaxhighlight>
 
unit unit1;
 
{$Mode ObjFPC}{$H+}
 
{$modeswitch systemcodepage} // прекращает действие директивы -FcUTF8
 
interface
 
const s: string[15] = 'äй';
 
end.
 
</syntaxhighlight>
 
 
 
В качестве альтернативы, возможно использовать строки shortstring с включенной директивой $codepage путем прямого присвоения кодов символов:
 
 
 
<syntaxhighlight>
 
unit unit1;
 
{$Mode ObjFPC}{$H+}
 
{$codepage utf8}
 
interface
 
const s: String[15] = #$C3#$A4; // ä
 
end.
 
</syntaxhighlight>
 
 
 
*WideString/UnicodeString/UTF8String работают только '''с''' {$codepage utf8} / -FcUTF8.
 
 
 
<syntaxhighlight>
 
unit unit1;
 
{$Mode ObjFPC}{$H+}
 
{$codepage utf8}
 
interface
 
const ws: WideString = 'äй';
 
end.
 
</syntaxhighlight>
 
 
 
== Запись в консоль операторами write ==
 
 
 
Некоторые консольные программы используют символы псевдографики, такие как '╩'. Например, в кодовой странице CP437 этот символ будет одним байтом с кодом '''#202''' и он чаще всего используется следующим образом:
 
 
 
<syntaxhighlight>
 
write('╩');
 
</syntaxhighlight>
 
 
 
Когда вы конвертируете такой исходник в UTF-8, например использованием всплывающего меню редактора Lazarus '''File Settings / Encoding / UTF-8''' и кликаньем в диалоговом окне кнопки "Change file on disk" (Изменить файл на диске), символ ''╩'' переводится в 3 байта (#226#149#169), так что литерал становится фактически строковым, а не символьным.
 
Процедуры ''write'' и ''writeln'' переведут строку из кодировки UTF-8 в текущую кодировку консоли. Так что ваша консольная программа правильно выведет знак '╩' при работе под Windows с любой кодовой страницей (т. е. не только с кодировкой CP437) и будет работать под  Linux и Mac OS X. Вы можете также использовать '╩'  в строках LCL, скажем, вот так: Memo1.Lines.Add('╩');
 
  
 
= Кодовые страницы FPC =
 
= Кодовые страницы FPC =
  
Компилятор (FPC) поддерживает указание кодовой страницы, которое может быть записано через опцию командной строки ''-Fc'' (т.е. ''-Fcutf8'') или эквивалентную директиву ''codepage'' (т.е. ''{$codepage utf8}''). В этом случае, перед тем, как копировать байты, представляющие строковые константы в тексте вашей программы, компилятор будет интерпретировать все символьные данные в соответствии с указанной кодовой страницей. Есть две вещи, которые не стоит упускать из виду:
+
Компилятор (FPC) поддерживает указание кодовой страницы, которое может быть записано через опцию командной строки ''-Fc'' (т.е. ''-Fcutf8'') или эквивалентную директиве ''codepage'' (т.е. ''{$codepage utf8}''). В этом случае, перед тем, как копировать байты, представляющие строковые константы в тексте вашей программы, компилятор будет интерпретировать все символьные данные в соответствии с указанной кодовой страницей. Есть две вещи, которые не стоит упускать из виду:
 
* На платформах Unix, менеджер широких строк должен быть обязательно включен добавлением юнита ''cwstring'' в перечень uses. Без него программа не сможет правильно преобразовывать строковые данные во время исполнения.
 
* На платформах Unix, менеджер широких строк должен быть обязательно включен добавлением юнита ''cwstring'' в перечень uses. Без него программа не сможет правильно преобразовывать строковые данные во время исполнения.
 
Менеджер широких строк добавляется по умолчанию в новом режиме UTF-8 RTL, однако это делает программу зависимой от libc и усложняет кросс-компиляцию.
 
Менеджер широких строк добавляется по умолчанию в новом режиме UTF-8 RTL, однако это делает программу зависимой от libc и усложняет кросс-компиляцию.
Line 520: Line 327:
 
Например:
 
Например:
  
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
 
program project1;
 
program project1;
 
{$codepage utf8}
 
{$codepage utf8}
Line 539: Line 346:
  
 
После компиляции и исполнения программа выведет:
 
После компиляции и исполнения программа выведет:
<pre>
+
<syntaxhighlight lang=pascal>
 
ä=ä
 
ä=ä
 
ä=ä
 
ä=ä
</pre>
+
</syntaxhighlight>
  
 
Причина в том, что после того как в строке обнаружен символ ''ä'', как упоминалось выше, оставшаяся часть строковой константы, присваиваемой переменной  'c', будет обработана как widestring. В результате #$C3 и #$A4 интерпретируются как widechar(#$C3) и widechar(#$A4), вместо прямой трансляции в байтовое представление.
 
Причина в том, что после того как в строке обнаружен символ ''ä'', как упоминалось выше, оставшаяся часть строковой константы, присваиваемой переменной  'c', будет обработана как widestring. В результате #$C3 и #$A4 интерпретируются как widechar(#$C3) и widechar(#$A4), вместо прямой трансляции в байтовое представление.
Line 550: Line 357:
 
* Символьные переменные в '''TFormatSettings'''  (баг [http://bugs.freepascal.org/view.php?id=27086 27086]): например: '''ThousandSeparator''', '''DecimalSeparator''', '''DateSeparator''', '''TimeSeparator''', '''ListSeparator'''. Эти поля должны быть заменены на строки для корректной поддержки UTF-8. Например, под Linux с установками LC_NUMERIC=ru_RU.utf8 разделитель тысяч состоит из двух байт nbsp/160.  
 
* Символьные переменные в '''TFormatSettings'''  (баг [http://bugs.freepascal.org/view.php?id=27086 27086]): например: '''ThousandSeparator''', '''DecimalSeparator''', '''DateSeparator''', '''TimeSeparator''', '''ListSeparator'''. Эти поля должны быть заменены на строки для корректной поддержки UTF-8. Например, под Linux с установками LC_NUMERIC=ru_RU.utf8 разделитель тысяч состоит из двух байт nbsp/160.  
 
** '''Обход проблемы:''' использовать только одиночные символы пробелов вместо того, что должно быть на самом деле, как это сделано в патче на баг [http://bugs.freepascal.org/view.php?id=27099 27099]
 
** '''Обход проблемы:''' использовать только одиночные символы пробелов вместо того, что должно быть на самом деле, как это сделано в патче на баг [http://bugs.freepascal.org/view.php?id=27099 27099]
 
  
 
=== Вызовы функций WinAPI в библиотеках FPC ===
 
=== Вызовы функций WinAPI в библиотеках FPC ===
Line 570: Line 376:
 
В сущности, LCL скорее всего придётся в будущем разделиться на две версии - одну для  UTF-8, и другую для UTF-16.
 
В сущности, LCL скорее всего придётся в будущем разделиться на две версии - одну для  UTF-8, и другую для UTF-16.
  
= FAQ =
+
= ЧаВо =
  
==Что насчёт режима DelphiUnicode?==
+
==Что насчет режима DelphiUnicode?==
  
Директива '''{$mode delphiunicode}''' была добавлена в FPC 2.7.1 и работает как комбинация ''{$Mode Delphi}'' совместно с ''{$ModeSwitch UnicodeStrings}''. См. следующий вопрос относительно ''ModeSwitch UnicodeStrings''.
+
'''{$Mode delphiunicode}''' был добавлен в FPC 2.7.1 и похож на ''{$Mode Delphi}'' с ''{$ModeSwitch UnicodeStrings}''. Смотрите следующий вопрос о ''ModeSwitch UnicodeStrings''.
 +
 
 +
==Как насчет ModeSwitch UnicodeStrings?==
 +
 
 +
'''{$ModeSwitch UnicodeStrings}''' был добавлен в FPC 2.7.1 и определяет «String» как «UnicodeString» (UTF-16), «Char» как «WideChar», «PChar» как «PWideChar» и так далее. Это влияет только на текущий модуль. Другие модули, в том числе используемые этим модулем, имеют свое собственное определение «String». Многие строки и типы RTL (например, TStringList) используют 8-битные строки, которые требуют преобразования из/в UnicodeString, которые автоматически добавляются компилятором. LCL использует строки UTF-8. Рекомендуется использовать исходники в кодировке UTF-8 с или без "-FcUTF8".
 +
 
 +
==Почему бы не использовать UTF8String в Lazarus?==
 +
 
 +
Кратко: потому что FCL не использует его.
 +
 
 +
Исчерпывающе:
 +
'''UTF8String''' определен в модуле '''system''' как
 +
<syntaxhighlight lang=pascal>UTF8String = type AnsiString(CP_UTF8);</syntaxhighlight>
 +
 
 +
Компилятор всегда предполагает, что он имеет кодировку UTF-8 (CP_UTF8), которая является многобайтовой кодировкой (то есть 1-4 байта на кодовую точку). Обратите внимание, что оператор [] обращается к байтам, а не к символам или кодовым точкам. То же самое для UnicodeString, но только слова вместо байтов.
 +
С другой стороны, предполагается, что '''String''' во время компиляции имеет ''DefaultSystemCodePage'' (CP_ACP). ''DefaultSystemCodePage'' определяется во время выполнения, поэтому компилятор консервативно полагает, что ''String'' и ''UTF8String'' имеют разные кодировки. Когда вы присваиваете или комбинируете ''String'' и ''UTF8String'', компилятор вставляет код преобразования. То же самое для ''ShortString'' и ''UTF8String''.
 +
 
 +
Lazarus использует FCL, который использует ''String'', поэтому использование ''UTF8String'' добавит конверсии. Если ''DefaultSystemCodePage'' не UTF-8, вы теряете символы. Если это UTF-8, то нет смысла использовать ''UTF8String''.
 +
 
 +
''UTF8String'' станет полезным, когда в конце концов появится FCL UTF-16.
 +
 
 +
==Почему UTF8String показывает странные символы, а String работает==
 +
 
 +
Например
 +
<syntaxhighlight lang=pascal>
 +
var s: UTF8String = 'ä';// с флагами по умолчанию (т.е. нет -Fc) это создает мусор
 +
                        // даже в системе UTF-8 Linux
 +
</syntaxhighlight>
  
==Что насчёт ModeSwitch UnicodeStrings?==
+
Вопрос: это ошибка? Ответ: Нет, потому что это работает как задокументировано.
  
Директива '''{$ModeSwitch UnicodeStrings}''' была добавлена в FPC 2.7.1 и определяет тип "String" синонимом "UnicodeString" (UTF-16), "Char" синонимом "WideChar", "PChar" синонимом "PWideChar" и т.д. Директива действует только на unit, в котором она объявлена. Другие юниты, имеющие в списке uses юнит с это директивой, имеют собственное определение типа "String". Многие строки и типы RTL (такие как TStringList) используют 8-битовые строки, что требует преобразований из/в UnicodeString, и необходимый для этого код автоматически добавляется компилятором. LCL использует строки UTF-8. Рекомендуется пользоваться исходниками в кодировке UTF-8 и компилировать с ключем "-FcUTF8".
+
FPC игнорирует переменную LANG для создания в каждой системе одинакового результата. По историческим причинам/Delphi-совместимости он использует ISO-8859-1 по умолчанию. Lazarus предпочитает UTF-8.
  
==Почему в Lazarus не используется строковый тип UTF8String?==
+
*Исходники UTF-8 работают со ''String'', потому что
 +
*# FPC по умолчанию не добавляет код преобразования для обычных строковых литералов.
 +
*# ''Исходная кодовая страница'' равна ''кодовой странице времени выполнения''. В Windows LazUTF8 устанавливает значение CP_UTF8.
 +
*UTF8String требует исходников UTF-8. Начиная с FPC 3.0 для UTF8String вы должны сообщать компилятору, что исходник является UTF8 (''-FcUTF8'', ''{$codepage UTF8}'' или сохранять файл как UTF-8 с BOM).
 +
{{Note| Если вы сообщаете компилятору исходную кодировку UTF-8, он заменяет все строковые литералы ASCII этого модуля на UTF-16, увеличивая размер двоичного файла, добавляя некоторые служебные данные, и PChar для литералов требует явного преобразования. Вот почему Lazarus не добавляет его по умолчанию.}}
  
Короткий ответ: потому что FCL не использует этот тип данных.
+
==Что случится, если я использую {$codepage utf8}?==
  
Развёрнутый ответ:
+
FPC имеет очень ограниченную поддержку UTF-8. Фактически, FPC поддерживает хранение литералов только как 8-битные строки с кодировкой «по умолчанию» или как widestrings (широкие строки). Поэтому любая кодовая страница не по умолчанию преобразуется в widestrings, даже если это системная кодовая страница. Например, большинство Linux/Mac/BSD используют UTF-8 в качестве системной кодовой страницы. Передача -Fcutf8 в компилятор сохранит строковый литерал как widestrings.
'''UTF8String''' определяется в unit '''system''' как
 
<syntaxhighlight>UTF8String          = type AnsiString(CP_UTF8);</syntaxhighlight>
 
  
Компилятор подразумевает, что такая строка всегда имеет кодировку UTF-8 (CP_UTF8), которая является мультибайтовой кодировкой (т.е. 1-4 байта на кодовую точку). Обратите внимание на то, что оператор [] на данных такого типа обеспечивает доступность байтов, а не символов и не кодовых точек. То же верно и для UnicodeString, только здесь доступны слова (тип word) вместо байтов.
+
Во время выполнения widestring литерал конвертируется. Когда вы присваиваете литерал AnsiString, widestring литерал преобразуется с помощью widestringmanager в кодировку системы. По умолчанию widestringmanager под Unix просто конвертирует widechars в символы, уничтожая любые символы не ASCII. Вы должны использовать widestringmanager, например, cwstring, чтобы получить правильное преобразование. Модуль LazUTF8 делает это.
С другой стороны, константам '''String''' во время компиляции присваивается маркировка кодовой страницы ''DefaultSystemCodePage'' (CP_ACP). ''DefaultSystemCodePage'' определяется во время исполнения, поэтому компилятор консервативно подразумевает, что ''String'' и ''UTF8String'' имеют различные кодировки. Когда вы присваиваете или комбинируете  ''String'' и ''UTF8String'', компилятор вставляет код преобразования. То же для ''ShortString'' и ''UTF8String''.
 
  
Lazarus использует FCL, повсеместно построенный на применении ''String'', поэтому использование ''UTF8String'' добавит дополнительные преобразования. Если ''DefaultSystemCodePage'' не в кодировке UTF-8, вы потеряете символы. Если же она в кодировке UTF-8, то и в этом случае нет никаких преимуществ в использовании отдельного типа ''UTF8String''.
+
= См. также =
  
''UTF8String'' станет полезной, когда FCL начнет работать со строками UTF-16.
+
Информация о Unicode, кодовых страницах, строковых типах и RTL в FPC. <br>
 +
Обратите внимание, что эта информация не совсем полезна для системы Lazarus UTF-8, потому что с точки зрения FPC она является хаком и меняет кодовую страницу по умолчанию. <br>
 +
[[FPC_Unicode_support/ru|Поддержка Юникода в FPC]]

Latest revision as of 05:51, 2 March 2020

English (en) 日本語 (ja) русский (ru)

Введение

Здесь описывается поддержка Unicode в «программах» Lazarus (консольных или серверных, без графического интерфейса) и «приложениях» (GUI с использованием LCL) при использовании особенностей FPC 3.0+.

Решение является кросс-платформенным и использует кодировку UTF-8, которая отличается от UTF-16 Delphi, но вы можете написать код, полностью совместимый с Delphi на уровне исходного кода, запомнив лишь несколько правил.

Поддержка Unicode включается автоматически для приложений LCL начиная с Lazarus 1.6.0 при компиляции с FPC 3.0+.

Старый метод поддержки UTF-8 в LCL при использовании FPC версий до 2.6.4 включительно, описан здесь: LCL Unicode Support

RTL с кодовой страницей UTF-8 по умолчанию

По умолчанию RTL использует системную кодовую страницу для AnsiStrings (в таких операциях, как например FileExists и TStringList.LoadFromFile). Под Windows это не-Unicode кодировка, поэтому могут использоваться только символы из текущей языковой группы (не более 256 символов). LCL, с другой стороны, работает с кодировкой UTF-8, охватывающей весь диапазон Unicode. Под Linux и macOS UTF-8 обычно является системной кодовой страницей, и здесь RTL использует по умолчанию CP_UTF8.

FPC, начиная с версии 3.0, обеспечивает API для изменения кодовой страницы RTL по умолчанию на другое значение. Lazarus (конкретно пакет LazUtils) использует данный API и изменяет кодовую страницу RTL по умолчанию на UTF-8 (CP_UTF8). Это означает, что пользователи Windows также могут теперь использовать строки UTF-8 в RTL.

  • Например, FileExists и StringList.LoadFromFile(Filename) теперь имеют полную поддержку Unicode. См. Полный перечень функций, которые полносттью поддерживают Unicode, здесь:

RTL changes

  • AnsiToUTF8, UTF8ToAnsi, SysToUTF8, UTF8ToSys не работают (не изменяют передаваемых данных). Эти функции обычно использовались для упомянутых выше функций RTL, которые больше не нуждаются в преобразованиях. Относящееся к функциям WinAPI, см ниже.
  • Многочисленные вызовы UTF8Encode и UTF8Decode больше не требуются, потому что такие действия при присваивании значений UnicodeString переменным типа String и наоборот компилятор делает автоматически.
  • При работе с WinAPI необходимо использовать "W"-функции или пользоваться функциями UTF8ToWinCP и WinCPToUTF8. То же верно для библиотек, которые до сих пор используют Ansi WinAPI функции. Например, в FPC 3.0 и более ранних версиях в этом нуждается unit registry.
  • "String" и "UTF8String" — различные типы. Если вы присваиваете значение String переменной типа UTF8String, компилятор добавляет код для проверки совпадения кодировок. Это будет стоить дополнительного времени исполнения и увеличит размер кода. Просто используйте String вместо UTF8String.
  • Консоль Windows использует кодировку, которая может отличаться от системной (в случае русского языка это всегда именно так). writeln в FPC 3.0+ автоматически преобразует строки UTF-8 в кодовую страницу консоли. Некоторые консольные программы Windows ожидают на входе строки в кодировке консоли и результаты своей работы также выдают в кодовой странице консоли. Для соответствующего преобразования можно использовать функции UTF8ToConsole и ConsoleToUTF8.


Дополнительная информация о новинках поддержки Unicode в FPC: FPC Unicode support

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

Следуйте простым правилам:

  • Используйте как обычно тип "String" вместо UTF8String или UnicodeString.
  • Всегда присваивайте константу переменной типа String.
  • Используйте тип UnicodeString явно для вызовов API, когда это необходимо.

Эти правила делают большую часть кода уже совместимым с Delphi при использовании настроек проекта по умолчанию.

Применение в Lazarus

Новый режим включается автоматически при компиляции FPC 3.0+. Это поведение может быть отключено директивой компиляции -dDisableUTF8RTL, детали см. на странице Lazarus with FPC3.0 without UTF-8 mode.

Если вы используете строковые литералы в новом режиме, ваши исходники всегда должны быть в кодировке UTF-8. Однако, ключ -FcUTF8 на самом деле обычно не требуется. Дополнительные сведения приведены ниже, в разделе "Строковые литералы".

Что же на самом деле происходит в новом режиме? В секции предварительной инициализации вызываются две FPC функции, устанавливающие кодировку строк по умолчанию в исполняющих библиотеках FPC в UTF-8 :

 SetMultiByteConversionCodePage(CP_UTF8);
 SetMultiByteRTLFileSystemCodePage(CP_UTF8);

Кроме того, функции UTF8...() из LazUTF8 (LazUtils) устанавливаются в качестве функций обратного вызова для функций RTL, имена которых начинаются с Ansi...().

Использование UTF-8 в программах без LCL

В не LCL-проекте добавьте зависимость для пакета LazUtils. Затем добавьте модуль LazUTF8 в секцию uses основного файла программы. Он должен быть в начале, сразу после критических менеджеров памяти и многопоточности (например, cmem, heaptrc, cthreads).

Вызов функций API, которые используют WideString или UnicodeString

Если тип параметра WideString или UnicodeString, вы можете просто передать ему строку. Компилятор преобразует данные автоматически. Появится предупреждение о преобразовании из AnsiString в UnicodeString, которое можно либо проигнорировать, либо подавить, приведя тип String к UnicodeString.

Переменная S в приведенных ниже примерах определяется как String, что здесь означает AnsiString.

procedure ApiCall(aParam: UnicodeString);  // Определение
 ...
ApiCall(S);                // вызов со строкой S, игнорируя предупреждение.
ApiCall(UnicodeString(S)); // вызов со строкой S, подавляя предупреждение (приведение String к UnicodeString).

Когда тип параметра является указателем PWideChar, вам нужна временная переменная UnicodeString. Присвойте ей свою строку. Затем компилятор преобразует эти данные. Затем введите временную переменную в PWideChar.

procedure ApiCallP(aParamP: PWideChar);  // Определение
 ...
var Tmp: UnicodeString;   // Временная переменная
 ...
Tmp := S;                 // Присваиваем String -> UnicodeString.
ApiCallP(PWideChar(Tmp)); // Вызов переменной temp, приведение к указателю
Light bulb  Примечание: в обоих случаях код совместим с Delphi. Это означает, что вы можете скопировать/вставить его в Delphi, и он сработает на 100% правильно. В Delphi String проецируется в UnicodeString.

Типичным случаем является вызов Windows API. Только должны вызываться их версии "W", потому что они поддерживают Unicode. Обычно используйте UnicodeString также с Windows API. WideString необходим только при программировании COM/OLE, где ОС заботится об управлении памятью.

Чтение / запись текстового файла с кодовой страницей Windows

Это не совместимо ни с Delphi, ни с прежним кодом Lazarus. На практике вы должны инкапсулировать код, связанный с системной кодовой страницей, и преобразовать данные в UTF-8 как можно быстрее.

Либо используйте RawByteString и сделайте явное преобразование...

uses ... , LConvEncoding;
 ...
var
  StrIn: RawByteString;
  StrOut: String;
 ...
StrOut := CP1252ToUTF8(StrIn,true);  // Использует фиксированную кодовую страницу
// или
StrOut := WinCPToUTF8(StrIn,true);  // Использует системную кодовую страницу на этом конкретном компьютере

... или установите правильную кодовую страницу для существующей строки

var
  StrIn: String;
 ...
SetCodePage(RawByteString(StrIn), 1252, false);  // Фиксированная 1252 (или Windows.GetACP())
Light bulb  Примечание: в строковой переменной должен быть какой-то текст. Пустая строка на самом деле является указателем Nil, и вы не можете установить ее кодовую страницу.

Windows.GetACP() возвращает системную кодовую страницу Windows.

Код, который очень сильно зависит от кодовой страницы Windows

Существует «план B» для кода, который очень сильно зависит от системной кодовой страницы Windows или должен записываться в консоль Windows за пределами кодовой страницы консоли.
См.: Lazarus с FPC3.0 без режима UTF-8.
К счастью, это нужно нечасто. В большинстве случаев проще конвертировать данные в UTF-8.

Запись в консоль

Вывод консоли Windows работает корректно, если ваши символы принадлежат кодовой странице консоли. Например, символ для рисования рамки '╩' в кодовой странице CP437 составляет один байт #202 и часто используется так:

write('╩');

Когда вы конвертируете код в UTF-8, например, используя пункт всплывающего меню редактора исходного кода Lazarus File Settings (Параметры файла) / Encoding (Кодировка) / UTF-8, и нажимая кнопку диалога "Change file on disk" (Изменить файл на диске), символ '╩' становится 3-х байтным (#226#149#169), поэтому литерал становится строкой.

Процедуры write и writeln преобразуют строку UTF-8 в текущую кодовую страницу консоли. Таким образом, ваша консольная программа теперь выводит '╩' в Windows с любой кодовой страницей (т.е. не только с CP437), и она даже работает в Linux и macOS. Вы также можете использовать '╩' в строках LCL, например, Memo1.Lines.Add('╩');

Если ваши символы не принадлежат кодовой странице консоли, все усложняется. Тогда вы вообще не сможете использовать новую систему Unicode.
См.: Проблема Системной кодировки и Консольной кодировки (Windows)

Символы Юникода и кодовые точки в коде

См. детали для UTF-8 в UTF8 strings and characters.

Функции кодовых точек для кодирования независимого кода

В пакете LazUtils есть модуль LazUnicode со специальными функциями для работы с кодовыми точками, независимо от кодировки. Они используют функции UTF8...() из LazUTF8 при использовании в режиме UTF-8, и функции UTF16...() из LazUTF16 при использовании в {$ModeSwitch UnicodeStrings} FPC или в Delphi (да, Delphi поддерживается!).

В настоящее время {$ModeSwitch UnicodeStrings} можно протестировать, задав "UseUTF16". Также есть тестовая программа LazUnicodeTest в каталоге components/lazutils/test. Она имеет 2 режима сборки, UTF8 и UTF16, для удобства тестирования. Тестовая программа также поддерживает Delphi, тогда, очевидно, используется режим UTF-16.

LazUnicode позволяет одному исходному коду работать между:

  • Lazarus с его UTF-8 решением.
  • Будущие FPC и Lazarus с Delphi-совместимым решением UTF-16.
  • Delphi, где String = UnicodeString.

Это обеспечивается перечисленными ниже функциями, не зависящим от кодирования:

  • CodePointCopy() - аналогична UTF8Copy()
  • CodePointLength() - аналогична UTF8Length()
  • CodePointPos() - аналогична UTF8Pos()
  • CodePointSize() - аналогична UTF8CharacterLength() (функция устарела. Вместо нее используйте UTF8CodepointSize. См. подробнее)
  • UnicodeToWinCP() - аналогична UTF8ToWinCP()
  • WinCPToUnicode() - аналогична WinCPToUTF8()

Этот режим также предоставляет перечислитель для кодовых точек, который компилятор использует для цикла for-in. В результате, независимо от кодировки, этот код работает:

 var s, ch: String;
 ...
 for ch in s do
   writeln('ch=',ch);

Delphi не предоставляет аналогичные функции для CodePoints в своем решении UTF-16. Практически большая часть кода Delphi рассматривает UTF-16 как кодировку с фиксированной шириной, что привело к возникновению большого количества неработающего кода UTF-16. Это означает, что использование LazUnicode также для Delphi улучшит качество кода!

Для использования Delphi необходимы оба модуля LazUnicode и LazUTF16.

Строковые литералы

Исходный код должен сохраняться в кодировке UTF-8. Lazarus создает такие файлы по умолчанию. Вы можете изменять кодировку импортированных файлов, щелкая правой кнопкой мыши в редакторе исходного кода / File Settings (Настройки файла) / Encoding (Кодировка).

Обычно директива {$codepage utf8} / -FcUTF8 не требуется. Это довольно нелогично, поскольку значение этого флага заключается в обработке строковых литералов как UTF-8. Однако новый режим UTF-8 переключает кодирование во время выполнения, а константы оцениваются во время компиляции.

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

Пример:

По правилу шаловливых ручек, используйте тип «String» и заставляйте литералы работать.

const s1 = 'äй';
const s2: string = #$C3#$A4; // ä
var s3;
...
  s3 := 'äöü';
Light bulb  Примечание: Строка UTF-8 может состоять из чисел, как это продемонстрировано для s2.
  • Литералы AnsiString/String работают как с директивой {$codepage utf8} / -FcUTF8 , так и без неё.
const s: string = 'äй';
  • Литералы ShortString работоспособны только без указания директивы {$codepage utf8} / -FcUTF8. Вы можете сделать следующее:
unit unit1;
{$Mode ObjFPC}{$H+}
{$modeswitch systemcodepage} // прекращает действие директивы -FcUTF8
interface
const s: string[15] = 'äй';
end.

В качестве альтернативы, возможно использовать строки shortstring с включенной директивой $codepage путем прямого присвоения кодов символов:

unit unit1;
{$Mode ObjFPC}{$H+}
{$codepage utf8}
interface
const s: String[15] = #$C3#$A4; // ä
end.
  • WideString/UnicodeString/UTF8String работают только с {$codepage utf8} / -FcUTF8.
unit unit1;
{$Mode ObjFPC}{$H+}
{$codepage utf8}
interface
const ws: WideString = 'äй';
end.

Присвоение строкового литерала другим строковым типам, кроме простого «String», более мудрено. Смотрите таблицы, что работает, а что нет.

Присвоение строковых литералов различным типам строк

Здесь работает означает правильную кодовую страницу и правильные кодовые точки. Кодовая страница 0 или кодовая страница 65001 - обе являются правильными, они означают UTF-8.

Без {$codepage utf8} или переключателя компилятора -FcUTF8

Тип String, исходник в UTF-8 Пример Const (в исходнике) Присвоение String Присвоение UTF8String Присвоение UnicodeString Присвоение CP1252String Присвоение RawByteString Присвоение ShortString Присвоение PChar
const const s = 'äöü'; working working wrong wrong wrong working working working
String const s: String = 'äöü'; working working working working working working working working
ShortString const s: String[15] = 'äöü'; working working working working wrong encoded working working not available
UTF8String const s: UTF8String = 'äöü'; wrong wrong wrong wrong wrong wrong wrong wrong
UnicodeString const s: UnicodeString = 'äöü'; wrong wrong wrong wrong wrong wrong wrong wrong
String с объявленной кодовой страницей type CP1252String = type AnsiString(1252); wrong wrong wrong wrong wrong wrong wrong wrong
RawbyteString const s: RawbyteString = 'äöü'; working working working working to codepage 0 changed working working working
PChar const c: PChar = 'äöü'; working working working working wrong working working working

С {$codepage utf8} или переключателем компилятора -FcUTF8

Тип String, исходник в UTF-8 Пример Const (в исходнике) Присвоение String Присвоение UTF8String Присвоение UnicodeString Присвоение CP1252String Присвоение RawByteString Присвоение ShortString Присвоение PChar
const const s = 'äöü'; UTF-16 encoded working working working working working working working
String const s: String = 'äöü'; working working working working working working working working
ShortString const s: String[15] = 'äöü'; wrong wrong wrong wrong wrong wrong wrong not available
UTF8String const s: UTF8String = 'äöü'; working working working working working working working working
UnicodeString const s: UnicodeString = 'äöü'; working working working working working working working wrong
String с объявленной кодовой страницей type CP1252String = type AnsiString(1252); working working working working working working wrong wrong
RawbyteString const s: RawbyteString = 'äöü'; working working working working to codepage 0 changed working working working
PChar const c: PChar = 'äöü'; wrong wrong wrong wrong wrong wrong wrong wrong

Помните, что присваивание между переменными разных типов строк всегда работает благодаря их динамическому кодированию в FPC 3+. Данные конвертируются автоматически при необходимости.
Только строковые литералы являются проблемой.

Переход из более старых версий Lazarus + LCL

Ранее (до 1.6.0) LCL поддерживал Unicode с выделенными функциями UTF8. Код не был совместим с Delphi.

Сейчас многие старые приложения LCL продолжают работать без изменений. Однако имеет смысл очистить код, чтобы сделать его более простым и более совместимым с Delphi. Код, который читает/записывает данные с использованием кодировки системной кодовой страницы Windows, нарушается и должен быть изменен. (См. Чтение / запись текстового файла с кодовой страницей Windows).

Явные функции преобразования необходимы только для ввода-вывода с данными кодовой страницы Windows или при вызове функций Windows Ansi. В противном случае FPC позаботится о автоматическом преобразовании кодировок. Для преобразования вашего старого кода предусмотрены пустые функции преобразования.

  • UTF8Decode, UTF8Encode - Почти все можно удалить.
  • UTF8ToAnsi, AnsiToUTF8 - Почти все можно удалить.
  • UTF8ToSys, SysToUTF8 - Почти все можно удалить. Теперь они являются пустышками и возвращают только свои параметры.

Файловые функции в RTL теперь заботятся о кодировке имен файлов. Все (?) связанные с именем файла функции ...UTF8() можно заменить на Delphi-совместимую функцию без суффикса UTF8. Например, FileExistsUTF8 можно заменить на FileExists.

Большинство строковых функций UTF8...() можно заменить на совместимые с Delphi функции Ansi...(). Например, UTF8UpperCase() -> AnsiUpperCase().

Теперь Unicode работает и в программах без GUI. Требуется только зависимость от LazUtils и размещение модуля LazUTF8 в разделе uses основного файла программы.

Для исторической справки, это была старая поддержка Unicode в LCL: Old LCL Unicode Support

Техническая реализация

Что на самом деле происходит в системе Unicode? Эти 2 функции FPC вызываются в разделе ранней инициализации, устанавливая кодировку String по умолчанию в FPC в UTF-8:

 SetMultiByteConversionCodePage(CP_UTF8);
 SetMultiByteRTLFileSystemCodePage(CP_UTF8);

В Windows функции UTF8...() в LazUTF8 (LazUtils) устанавливаются в качестве бэкэндов для строковых функций RTL Ansi...(). Таким образом, эти функции работают совместимым с Delphi способом.

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

Компилятор (FPC) поддерживает указание кодовой страницы, которое может быть записано через опцию командной строки -Fc (т.е. -Fcutf8) или эквивалентную директиве codepage (т.е. {$codepage utf8}). В этом случае, перед тем, как копировать байты, представляющие строковые константы в тексте вашей программы, компилятор будет интерпретировать все символьные данные в соответствии с указанной кодовой страницей. Есть две вещи, которые не стоит упускать из виду:

  • На платформах Unix, менеджер широких строк должен быть обязательно включен добавлением юнита cwstring в перечень uses. Без него программа не сможет правильно преобразовывать строковые данные во время исполнения.

Менеджер широких строк добавляется по умолчанию в новом режиме UTF-8 RTL, однако это делает программу зависимой от libc и усложняет кросс-компиляцию.

  • Компилятор преобразует все строковые константы, содержащие символы, не относящиеся к ASCII, в константы типа widestring. Затем они автоматически преобразуются обратно в ansistring (либо во время компиляции, либо во время исполнения), но это может привести к искажениям, если вы попытаетесь смешать в одной строковой константе символы, напечатанные в тексте исходника и символы, заданные числовым представлением:

Например:

program project1;
{$codepage utf8}
{$mode objfpc}{$H+}
{$ifdef unix}
uses cwstring;
{$endif}
var
  a,b,c: string;
begin
  a:='ä';
  b:='='#$C3#$A4; // #$C3#$A4 - закодированный в UTF-8 символ ä
  c:='ä='#$C3#$A4; // после не относящегося к  ascii 'ä' компилятор интерпретирует #$C3 в качестве отдельного widechar.
  writeln(a,b); // выводит ä=ä
  writeln(c);   // выводит ä=ä
end.

После компиляции и исполнения программа выведет:

ä=ä
ä=ä

Причина в том, что после того как в строке обнаружен символ ä, как упоминалось выше, оставшаяся часть строковой константы, присваиваемой переменной 'c', будет обработана как widestring. В результате #$C3 и #$A4 интерпретируются как widechar(#$C3) и widechar(#$A4), вместо прямой трансляции в байтовое представление.

Открытые вопросы

  • Символьные переменные в TFormatSettings (баг 27086): например: ThousandSeparator, DecimalSeparator, DateSeparator, TimeSeparator, ListSeparator. Эти поля должны быть заменены на строки для корректной поддержки UTF-8. Например, под Linux с установками LC_NUMERIC=ru_RU.utf8 разделитель тысяч состоит из двух байт nbsp/160.
    • Обход проблемы: использовать только одиночные символы пробелов вместо того, что должно быть на самом деле, как это сделано в патче на баг 27099

Вызовы функций WinAPI в библиотеках FPC

  • Unit registry, TRegistry - этот unit использует Ansi функции Windows API и поэтому вам придётся пользоваться UTF8ToWinCP, WinCPToUTF8. Раньше для этого требовался вызов UTF8ToSys.
  • Все вызовы Windows функций с Ansi API в библиотеках FPC должны быть заменены версией W-API. Это в любом случае будет сделано для будущей поддержки UTF-16, поэтому никакого конфликта интересов нет. (прим. перев. - однако, эти действия означают декларированный отказ от дальнейшей поддержки версий Windows с неполной поддержкой Unicode - Windows 98, 95 и более ранних, а также некоторых мобильных и встроенных)
  • TProcess - под Windows TProcess FPC 3.0 поддерживает только системную кодовую страницу. Необходимо либо использовать TProcessUTF8 из unit utf8process или патчить FPC, см. баг 29136

ToDo: Перечислить все относящиеся к багтрекеру FPC проблемы и патчи, которые могут эти проблемы решить.

Будущее

Целью проекта FPC является создание решения, базирующегося на Delphi-совместимом UnicodeString (UTF-16), но пока мы к этому не готовы. Потребуется длительное время для такой реализации.

Реализацию LCL на базе UTF-8 в её имеющемся виде необходимо рассматривать как временное решение. В будущем, когда в FPC будет полная поддержка UnicodeString как в RTL, так и в FCL, проект Lazarus обеспечит решения для LCL, использующее эти возможности. В то же время целью является и сохранение поддержки UTF-8, несмотря на то, что это может потребовать изменения в строковых типах или чего-то ещё. Деталей пока не знает никто. Мы обязательно сообщим вам о них, когда станет известно...

В сущности, LCL скорее всего придётся в будущем разделиться на две версии - одну для UTF-8, и другую для UTF-16.

ЧаВо

Что насчет режима DelphiUnicode?

{$Mode delphiunicode} был добавлен в FPC 2.7.1 и похож на {$Mode Delphi} с {$ModeSwitch UnicodeStrings}. Смотрите следующий вопрос о ModeSwitch UnicodeStrings.

Как насчет ModeSwitch UnicodeStrings?

{$ModeSwitch UnicodeStrings} был добавлен в FPC 2.7.1 и определяет «String» как «UnicodeString» (UTF-16), «Char» как «WideChar», «PChar» как «PWideChar» и так далее. Это влияет только на текущий модуль. Другие модули, в том числе используемые этим модулем, имеют свое собственное определение «String». Многие строки и типы RTL (например, TStringList) используют 8-битные строки, которые требуют преобразования из/в UnicodeString, которые автоматически добавляются компилятором. LCL использует строки UTF-8. Рекомендуется использовать исходники в кодировке UTF-8 с или без "-FcUTF8".

Почему бы не использовать UTF8String в Lazarus?

Кратко: потому что FCL не использует его.

Исчерпывающе: UTF8String определен в модуле system как

UTF8String = type AnsiString(CP_UTF8);

Компилятор всегда предполагает, что он имеет кодировку UTF-8 (CP_UTF8), которая является многобайтовой кодировкой (то есть 1-4 байта на кодовую точку). Обратите внимание, что оператор [] обращается к байтам, а не к символам или кодовым точкам. То же самое для UnicodeString, но только слова вместо байтов. С другой стороны, предполагается, что String во время компиляции имеет DefaultSystemCodePage (CP_ACP). DefaultSystemCodePage определяется во время выполнения, поэтому компилятор консервативно полагает, что String и UTF8String имеют разные кодировки. Когда вы присваиваете или комбинируете String и UTF8String, компилятор вставляет код преобразования. То же самое для ShortString и UTF8String.

Lazarus использует FCL, который использует String, поэтому использование UTF8String добавит конверсии. Если DefaultSystemCodePage не UTF-8, вы теряете символы. Если это UTF-8, то нет смысла использовать UTF8String.

UTF8String станет полезным, когда в конце концов появится FCL UTF-16.

Почему UTF8String показывает странные символы, а String работает

Например

var s: UTF8String = 'ä';// с флагами по умолчанию (т.е. нет -Fc) это создает мусор
                        // даже в системе UTF-8 Linux

Вопрос: это ошибка? Ответ: Нет, потому что это работает как задокументировано.

FPC игнорирует переменную LANG для создания в каждой системе одинакового результата. По историческим причинам/Delphi-совместимости он использует ISO-8859-1 по умолчанию. Lazarus предпочитает UTF-8.

  • Исходники UTF-8 работают со String, потому что
    1. FPC по умолчанию не добавляет код преобразования для обычных строковых литералов.
    2. Исходная кодовая страница равна кодовой странице времени выполнения. В Windows LazUTF8 устанавливает значение CP_UTF8.
  • UTF8String требует исходников UTF-8. Начиная с FPC 3.0 для UTF8String вы должны сообщать компилятору, что исходник является UTF8 (-FcUTF8, {$codepage UTF8} или сохранять файл как UTF-8 с BOM).
Light bulb  Примечание: Если вы сообщаете компилятору исходную кодировку UTF-8, он заменяет все строковые литералы ASCII этого модуля на UTF-16, увеличивая размер двоичного файла, добавляя некоторые служебные данные, и PChar для литералов требует явного преобразования. Вот почему Lazarus не добавляет его по умолчанию.

Что случится, если я использую {$codepage utf8}?

FPC имеет очень ограниченную поддержку UTF-8. Фактически, FPC поддерживает хранение литералов только как 8-битные строки с кодировкой «по умолчанию» или как widestrings (широкие строки). Поэтому любая кодовая страница не по умолчанию преобразуется в widestrings, даже если это системная кодовая страница. Например, большинство Linux/Mac/BSD используют UTF-8 в качестве системной кодовой страницы. Передача -Fcutf8 в компилятор сохранит строковый литерал как widestrings.

Во время выполнения widestring литерал конвертируется. Когда вы присваиваете литерал AnsiString, widestring литерал преобразуется с помощью widestringmanager в кодировку системы. По умолчанию widestringmanager под Unix просто конвертирует widechars в символы, уничтожая любые символы не ASCII. Вы должны использовать widestringmanager, например, cwstring, чтобы получить правильное преобразование. Модуль LazUTF8 делает это.

См. также

Информация о Unicode, кодовых страницах, строковых типах и RTL в FPC.
Обратите внимание, что эта информация не совсем полезна для системы Lazarus UTF-8, потому что с точки зрения FPC она является хаком и меняет кодовую страницу по умолчанию.
Поддержка Юникода в FPC