"О чём не пишут в книгах по Delphi" - читать интересную книгу автора (Григорьев А. Б.)

1.1.12. ANSI и Unicode

Windows поддерживает две кодировки: ANSI и Unicode. В кодировке ANSI (American National Standard Institute) каждый символ кодируется однобайтным кодом. Коды от 0 до 127 совпадают с кодами ASCII, коды от 128 до 255 могут означать разные символы различных языков в зависимости от выбранной кодовой страницы. Кодовые страницы позволяют уместить многочисленные символы различных языков в однобайтный код, но при этом можно работать только с одной кодовой страницей, т.е. с одним языком. Неверно выбранная кодовая страница приводит к появлению непонятных символов (в Интернете их обычно называют "кракозябрами") вместо осмысленного текста.

В кодировке Unicode используется 2 байта на символ, что даёт возможность закодировать 65 536 символов. Этого хватает для символов латиницы и кириллицы, греческого алфавита, китайских иероглифов, арабских и еврейских букв, а также многочисленных дополнительных (финансовых, математических и т. п.) символов. Кодовых страниц в Unicode нет.

Примечание

Кодовая страница русского языка в ANSI имеет номер 1251. Кодировка символов в ней отличается от принятой в DOS так называемой альтернативной кодировки. В целях совместимости для DOS-программ, а также для консольных приложений Windows использует альтернативную кодировку. Именно поэтому при выводе русского текста в консольных приложениях получаются те самые "кракозябры". Чтобы избежать этого, следует перекодировать символы из кодировки ANSI в DOS при выводе, и наоборот — при вводе. Это можно сделать с помощью функций CharToOem и OemToChar.

Windows NT/2000/XP поддерживает ANSI и Unicode в полном объеме. Это значит, что любая функция, работающая со строками, представлена в двух вариантах: для ANSI и для Unicode. Windows 9x/МЕ в полном объеме поддерживает только ANSI. Unicode-варианты в этих системах есть у относительно небольшого числа функций. Каждая страница MSDN, посвященная функции, работающей со строками (или со структурами, содержащими строки), в нижней части содержит надпись, показывающую, реализован ли Unicode-вариант этой функции только для NT/2000/XP или для всех платформ.

Примечание

В качестве примера рассмотрим функции вывода текста на экран. Unicode-версию на всех платформах имеют только две из них TextOut и ExtTextOut. Функции DrawText и DrawTextEx имеют Unicode-варианты только в Windows NT/2000/XP. Если же смотреть функции для работы с окнами, то среди них нет ни одной, которая имела бы Unicode-вариант в Windows 9х/МЕ. Следует отметить, что с относительно недавнего времени Microsoft предоставляет расширение для Windows 9x/МЕ которое позволяет добавить полную поддержку Unicode в эти системы. Это расширение называется MSLU (Microsoft Layer for Unicode), его можно скачать с официального сайта Microsoft.

Рассмотрим, как сосуществуют два варианта на примере функции RegisterWindowMessage. Согласно справке, она экспортируется библиотекой user32.dll. Однако если посмотреть список функций, экспортируемых этой библиотекой (это можно сделать, например, при помощи утилиты TDump.exe, входящей в состав Delphi), то там этой функции не будет, зато будут функции RegisterWindowMessageA и RegisterWindowMessageW. Первая из них — это ANSI-вариант функции, вторая — Unicode-вариант (буква W означает Wide — широкий; символы кодировки Unicode часто называются широкими из-за того, что на один символ приходится не один, а два байта).

Сначала разберемся с тем, как используются два варианта одной функции в Microsoft Visual C++. В стандартных заголовочных файлах учитывается наличие макроопределения UNICODE. Есть два символьных типа — CHAR для ANSI и WCHAR для Unicode. Если макрос UNICODE определен, тип TCHAR соответствует типу WCHAR, если не определен — типу CHAR (после этого производные от TCHAR типы, такие как LPCTSTR автоматически начинают соответствовать кодировке, определяемой наличием или отсутствием определения UNICODE). В заголовочных файлах импортируются оба варианта функции, а также определяется макрос RegisterWindowMessage. Его смысл также зависит от макроса UNICODE: если он определен, RegisterWindowMessage эквивалентен RegisterWindowMessageW, если не определен — RegisterWindowMessageA. Все функции, поддерживающие два варианта кодировки, импортируются точно так же. Таким образом, вставляя или убирая макроопределение UNICODE, можно, не меняя ни единого символа в программе, компилировать ее ANSI- или Unicode-версию.

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

Рассмотрим, как та же функция RegisterWindowMessage импортируется модулем Windows (листинг 1.19).

Листинг 1.19. Импорт функции RegisterWindowMessage

interface

...

function RegisterWindowMessage(lpString: PChar): UINT; stdcall;

function RegisterWindowMessageA(lpString: PAnsiChar): UINT;

stdcall; function RegisterWindowMessageW(lpString: PWideChar): UINT; stdcall;

...

implementation

...

function RegisterWindowMessage;

 external user32 name 'RegisterWindowMessageA';

function RegisterWindowMessageA;

 external user32 name 'RegisterWindowMessageA';

function RegisterWindowMessageW;

 external user32 name 'RegisterWindowMessageW';

Видно, что функция RegisterWindowMessageA импортируется дважды: один раз под своим настоящим именем, а второй раз — под именем RegisterWindowMessage. Любое из этих имен подходит для вызова ANSI-варианта этой функции (напоминаю, что типы PChar и PAnsiChar эквивалентны). Чтобы вызвать Unicode-вариант функции, потребуется функция RegisterWindowMessageW.

Структуры, содержащие строковые данные, также имеют ANSI- и Unicode-вариант. Например, структура WNDCLASS в модуле Windows представлена типами TWndClassA (с синонимами WNDCLASSA и tagWNDCLASSA) и TWndClassW (с синонимами WNDCLASSW и tagWHDCLASSW). Тип TWndClass (и его синонимы WNDCLASS и tagWNDCLASS) эквивалентен типу TWndClassA. Соответственно, при вызове функций RegisterClassA и RegisterClassExA используется тип TWndClassA, при вызове RegisterClassW и RegisterClassExW — тип TWndClassW.