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

From Lazarus wiki
Jump to navigationJump to search
Line 57: Line 57:
 
В не LCL-проекте добавьте зависимость для пакета '''LazUtils'''. Затем добавьте модуль '''LazUTF8''' в секцию uses основного файла программы. Он должен быть в начале, сразу после критических менеджеров памяти и многопоточности (например, cmem, heaptrc, cthreads).
 
В не LCL-проекте добавьте зависимость для пакета '''LazUtils'''. Затем добавьте модуль '''LazUTF8''' в секцию uses основного файла программы. Он должен быть в начале, сразу после критических менеджеров памяти и многопоточности (например, cmem, heaptrc, cthreads).
  
= Calling API functions that use WideString or UnicodeString =
+
= Вызов функций API, которые используют WideString или UnicodeString =
  
When a parameter type is WideString or UnicodeString, you can just pass a String to it. The compiler converts data automatically. There will be a warning about converting from AnsiString to UnicodeString which can be either ignored or suppressed by typecasting the String to UnicodeString.
+
Если тип параметра WideString или UnicodeString, вы можете просто передать ему строку. Компилятор преобразует данные автоматически. Появится предупреждение о преобразовании из AnsiString в UnicodeString, которое можно либо проигнорировать, либо подавить, приведя тип String к UnicodeString.
  
Variable S in examples below is defined as String which here means AnsiString.
+
Переменная S в приведенных ниже примерах определяется как String, что здесь означает AnsiString.
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
procedure ApiCall(aParam: UnicodeString);  // Definition.
+
procedure ApiCall(aParam: UnicodeString);  // Определение
 
  ...
 
  ...
ApiCall(S);                // Call with String S, ignore warning.
+
ApiCall(S);                // вызов со строкой S, игнорируя предупреждение.
ApiCall(UnicodeString(S)); // Call with String S, suppress warning.
+
ApiCall(UnicodeString(S)); // вызов со строкой S, подавляя предупреждение (приведение String к UnicodeString).
 
</syntaxhighlight>
 
</syntaxhighlight>
  
When a parameter type is a pointer PWideChar, you need a temporary UnicodeString variable. Assign your String to it. The compiler then converts its data. Then typecast the temporary variable to PWideChar.
+
Когда тип параметра является указателем PWideChar, вам нужна временная переменная UnicodeString. Присвойте ей свою строку. Затем компилятор преобразует эти данные. Затем введите временную переменную в PWideChar.
<syntaxhighlight>
+
<syntaxhighlight lang=pascal>
procedure ApiCallP(aParamP: PWideChar);  // Definition.
+
procedure ApiCallP(aParamP: PWideChar);  // Определение
 
  ...
 
  ...
var Tmp: UnicodeString;  // Temporary variable.
+
var Tmp: UnicodeString;  // Временная переменная
 
  ...
 
  ...
Tmp := S;                // Assign String -> UnicodeString.
+
Tmp := S;                // Присваиваем String -> UnicodeString.
ApiCallP(PWideChar(Tmp)); // Call with temp variable, typecast to pointer.
+
ApiCallP(PWideChar(Tmp)); // Вызов переменной temp, приведение к указателю
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Note, in both cases the code is compatible with Delphi. It means you can copy/paste it to Delphi and it works 100% correctly. In Delphi String maps to UnicodeString.
+
{{Note| в обоих случаях код совместим с Delphi. Это означает, что вы можете скопировать/вставить его в Delphi, и он сработает на 100% правильно. В Delphi String проецируется в UnicodeString.}}
  
A typical case is a Windows API call. Only the "W" versions of them should be called because they support Unicode.
+
Типичным случаем является вызов Windows API. Только должны вызываться их версии "W", потому что они поддерживают Unicode.
Normally use UnicodeString also with Windows API. WideString is only needed with COM/OLE programming where the OS takes care of memory management.
+
Обычно используйте UnicodeString также с Windows API. WideString необходим только при программировании COM/OLE, где ОС заботится об управлении памятью.
  
 
==================================
 
==================================
 +
 
= Совместимость с Unicode Delphi =
 
= Совместимость с Unicode Delphi =
  

Revision as of 21:17, 4 September 2019

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 и Mac OS X 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, где ОС заботится об управлении памятью.

======================

Совместимость с 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 , так и без неё.
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.

Запись в консоль операторами write

Некоторые консольные программы используют символы псевдографики, такие как '╩'. Например, в кодовой странице 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 и Mac OS X. Вы можете также использовать '╩' в строках LCL, скажем, вот так: Memo1.Lines.Add('╩');

Кодовые страницы 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.

FAQ

Что насчёт режима 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" и т.д. Директива действует только на unit, в котором она объявлена. Другие юниты, имеющие в списке uses юнит с это директивой, имеют собственное определение типа "String". Многие строки и типы RTL (такие как TStringList) используют 8-битовые строки, что требует преобразований из/в UnicodeString, и необходимый для этого код автоматически добавляется компилятором. LCL использует строки UTF-8. Рекомендуется пользоваться исходниками в кодировке UTF-8 и компилировать с ключем "-FcUTF8".

Почему в Lazarus не используется строковый тип UTF8String?

Короткий ответ: потому что FCL не использует этот тип данных.

Развёрнутый ответ: UTF8String определяется в unit system как

UTF8String          = type AnsiString(CP_UTF8);

Компилятор подразумевает, что такая строка всегда имеет кодировку UTF-8 (CP_UTF8), которая является мультибайтовой кодировкой (т.е. 1-4 байта на кодовую точку). Обратите внимание на то, что оператор [] на данных такого типа обеспечивает доступность байтов, а не символов и не кодовых точек. То же верно и для UnicodeString, только здесь доступны слова (тип word) вместо байтов. С другой стороны, константам 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.