    UNICODE 

    глава 17 

    Microsoft Windows становится все популярнее, и нам, разработчикам, надо 
все больше ориентироваться на международный рынок. Раньше считалось 
нормальным, что локализованные версии программных продуктов выходят спустя 
полгода после их появления в США. Но расширение поддержки в операционной 
системе множества самых разных языков упрощает выпуск программ, рассчитанных 
на международный рынок, и тем самым сокращает задержки с началом их дистрибуции. 
    В Windows есть средства, помогающие разработчикам локализовать свои 
приложения. Программа получает специфичную для конкретной страны информацию — 
региональные стандарты, — вызывая различные У^т32-функщ1И, и узнает 
предпочтения пользователя, анализируя параметры, заданные в Control Panel 
(Панель управления). Кроме того, Windows поддерживает массу всевозможных шрифтов. 

    Наборы символов 

    Операции с различными наборами символов всегда были настоящей проблемой 
при локализации. Годами кодируя текстовые строки как последовательности 
однобайтовых символов с нулем в конце, большинство программистов к этому так 
привыкло, что это стало чуть ли не второй их натурой. Вызываемая нами функция 
strlen возвращает количество символов в заканчивающемся нулем массиве 
однобайтовых символов. Но существуют такие языки и системы письменности 
(классический пример — кана), в которых столько знаков, что одного байта, 
позволяющего кодировать не более 256 символов, просто недостаточно. Для 
поддержки подобных языков были созданы двухбайтовые наборы символов 
(double-byte character sets, DBCS). 

    Одно- и двухбайтовые наборы символов 

    В двухбайтовом наборе символ представляется либо одним, либо двумя 
байтами. Для японской каны, если значение первого байта находится между 0х81 и 
Ox9F или между ОхЕО и OxFC, надо проверить значение следующего байта в строке, 
чтобы определить полный символ. Работа с двухбайтовыми наборами символов — 
просто кошмар для программиста, так как часть их состоит из одного байта, а 
часть — из двух. 
    Простой вызов strlen не дает количества символов в строке — она возвращает 
только число байтов. В ANSI-варианте стандартной библиотеки С нет функций, 
работающих с двухбайтовыми наборами символов. Но в аналогичную библиотеку 
Visual C++ включено множество функций (типа _mbslen), способных оперировать со 
строками мультибайтовых символов (т. е. как одно-, так и двухбайтовых символов). 

    581 

    582 ___________________________Windows для профессионалов 

    Для работы с DBCS-символами в Win32 предусмотрен целый набор 
вспомогательных функций: 

    Функция                                  Описание 

    LPTSTR CharNext(LPCTSTR IpszCurrentChar);    Возвращает адрес следующего 
символа в строке. 
    LPTSTR CharPrev(LPCTSTR IpszStart,           Возвращает адрес предыдущего 
символа в строке. LPCTSTR IpszCurrentChar); 
    BOOL IsDBCSLeadByte(BYTE bTestChar);        Возвращает TRUE, если данный 
байт — первый байт DBCS-символа. 

    Функции CharNext и CharPrev позволяют «перемещаться» по двухбайтовой 
строке на символ вперед или назад, a IsDBCSLeadByte возвращает TRUE, если 
переданный ей байт — первый байт двухбайтового символа. 
    Хотя эти функции несколько облегчают работу с DBCS-строками, необходимость 
в ином подходе очевидна. Познакомимся с Unicode. 
    Когда Microsoft переносила функции AnsiNext и AnsiPrev в Win32, кто-то 
сообразил, что они работают на самом деле с DBCS-, а не ANSI-строками и 
поэтому их надо переименовать. Так что AnsiNext я AnsiPrev включены в Win32 
API под именами CharNext и CharPrev. В заголовочных файлах Win32 — для 
упрощения переноса 16-разрядных приложений — определены макросы: 

    «define AnsiNext CharNextA 
    Sdefine AnsiPrev CharPrevA 
    Unicode: набор «широких» символов 

    Unicode — стандарт, первоначально разработанный фирмами Apple и Xerox в 
1988 году. В 1991 году был создан консорциум по совершенствованию и внедрению 
Unicode, в который вошли Adobe, Aldus, Apple, Borland, Digital, Go, IBM, 
Lotus, Metaphor, Microsoft, NeXT, Novell, Research Libraries Group, Sun, 
Taligent, Unisys, WordPerfect и Xerox. Эта группа компаний наблюдает за 
соблюдением стандарта Unicode, полное описание которого Вы найдете в книге The 
Unicode Standard: Worldwide Character Encoding (издательство Addison-Wesley). 
    Строки в Unicode просты и логичны. Все символы в них представлены 
16-битными значениями (по 2 байта на каждый). В них нет особых байтов, 
указывающих, чем является следующий байт: частью того же символа или новым 
символом. Это значит, что прохождение по строке реализуется простым 
приращением указателя — положительным или отрицательным. Функции CharNext, 
CharPrev и IsDBCSLeadByte больше не нужны. 
    Так как каждый символ — 16-битное число, Unicode позволяет кодировать 
свыше 65 000 символов, что более чем достаточно для работы с любым языком. 
Разительное отличие от 256 знаков, доступных в однобайтовом наборе! 
    В настоящее время кодовые позиции (code points)' определены для арабского, 
китайского, греческого, еврейского, латинского (английского) алфавитов, а 
также для кириллицы (русского), японской каны, корейского хангыль и некоторых 
других алфавитов. Кроме того, в набор символов включено большое количество 
знаков препинания, математических и технических символов, стрелок, 
диакритических и других знаков. Все вместе они занимают около 34 000 кодовых 
позиций, что оставляет перспективы для будущих расширений. 
    1. Кодовая позиция — позиция знака в наборе символов. 

    _____________________________________Глава 17. Unicode 583 

    Эти 65 536 символов разбиты на отдельные группы. Некоторые группы, а также 
включенные в них символы показаны в таблице: 

    16-битный код     Символы 

    0000-007F          ASCII 
    0080-OOFF         Символы Latini 
    0100-017F          Европейские латинские 
    0180-0 IFF         Расширенные латинские 
    0250-02AF         Стандартные фонетические 
    02BO-02FF         Модифицированные литеры 
    0300-036F         Общие диакритические знаки 
    0370-03FF          Греческий 
    0400-04FF         Кириллица 
    0530-058F         Армянский 
    0590-05FF          Еврейский 
    0600-06FF          Арабский 
    0900-097F          Деванагари 

    Около 29 000 кодовых позиций пока не заняты, но зарезервированы на 
будущее. Примерно 6 000 позиций оставлено специально для программистов (на их усмотрение). 

    Почему Unicode? 

    Разрабатывая приложение, присмотритесь к преимуществам Unicode. Даже если 
Вы пока не собираетесь локализовать программный продукт, разработка с прицелом 
на Unicode упростит эту задачу в будущем. Кроме того, Unicode: 
    • допускает обмен данными на разных языках; 
    • позволяет поставлять единственный двоичный ЕХЕ- или DLL-файл, 
поддерживающий все языки; 
    • увеличивает эффективность прикладных программ (об этом мы поговорим чуть позже). 
    Как написать программу с использованием Unicode 
    Microsoft разработала Win32 API так, чтобы как можно меньше повлиять на 
Ваш код. В самом деле, у Вас появилась возможность создать единственный файл с 
исходным кодом, компилируемый как с применением Unicode, так и без него. Для 
перехода на Unicode надо лишь, определив 2 макроса, перекомпилировать исходный текст. 
    Windows NT и Unicode 
    Windows NT — первая операционная система, целиком и полностью построенная 
на Unicode. Все внутренние функции для создания окон, вывода текста, операций 
со строками и т. д. ожидают передачи строковых параметров в Unicode. Если 
какой-то Win32^yHKHHH 

    584 _____________________________Windows для профессионалов 

    передается ANSI-строка, она сначала преобразуется в Unicode и лишь потом 
передается операционной системе. Если Вы ждете результата функции в виде 
ANSI-строки, операционная система преобразует строку — перед возвратом в 
приложение — из Unicode в ANSI. Все эти преобразования протекают скрытно от 
Вас. Но, конечно, на преобразование строк тратится лишнее время — пусть и незначительное. 
    Например, функция CreateWindowEx, вызываемая с ANSI-строками для имени 
класса и заголовка окна, должна, выделив дополнительные блоки памяти (в 
стандартной куче Вашего процесса), преобразовать эти строки в Unicode и, 
сохранив результат в выделенных блоках памяти, вызвать Unicode-версию CreateWindowEx. 
    Для функций, заполняющих строками выделенные программой буферы, системе — 
прежде чем программа сможет их обрабатывать — нужно преобразовать строки из 
Unicode в ANSI. Из-за этого Ваше приложение потребует больше памяти и будет 
работать медленнее. Поэтому гораздо эффективнее разрабатывать программу, с 
самого начала ориентируясь на Unicode. 

    Windows 95 и Unicode 

    Windows 95 — не совсем новая операционная система. У нее «16-разрядное 
наследство», которое не поддерживает Unicode. Введение поддержки Unicode в 
Windows 95 было бы слишком трудоемкой задачей, и при разработке этой 
операционной системы от нее отказались. По этой причине вся внутренняя 
обработка строк в Windows 95, как и у ее предшественниц, построена на 
применении ANSI. 
    И все же Windows 95 вполне допускает работу с Win32-пpилoжeниями, 
обрабатывающими символы и строки в Unicode, хотя вызов Win32-фyнкций при этом 
заметно усложняется. Например, если Вы, обращаясь к CreateWindowEx, передаете 
ей ANSI-строки, вызов проходит очень быстро — не требуется ни выделения 
буферов, ни преобразования строк. Но стоит пожелать вызвать Create WindowEx с 
Unicode-строками, и Вам придется самому выделять буферы, явно вызывать 
\\[т32-функции, преобразующие строки из Unicode в ANSI, обращаться к 
CreateWindowEx, снова вызывать \^т32-ф'унки.ии, преобразующие строки — на этот 
раз из ANSI в Unicode, и освобождать временные буферы. Так что в Windows 95 
работать с Unicode не столь удобно, как в Windows NT. Подробнее о 
преобразованиях строк в Windows 95 я расскажу в конце главы. 

    Unicode и стандартная библиотека С 

    Для использования Unicode-строк были введены некоторые новые типы данных. 
Стандартный заголовочный файл STRING.H модифицирован: в нем определен wchar_t 
— тип данных Unicode-символа: 

    typedef unsigned short wchar_t: 

    Если Вы хотите, скажем, создать буфер для хранения Unicode-строки длиной 
до 99 символов плюс нулевой символ в конце, поставьте оператор: 

    wchar_t szBuffer[100]; 

    Он создает массив из 100 16-битных значений. Конечно, стандартные функции 
для работы со строками вроде strcpy, strchr и strcat оперируют только с 
ANSI-строками — они неспособны корректно обрабатывать Unicode-строки. Поэтому 
разработан новый, дополнительный набор функций. На рис. 17-1 приведен список 
строковых функций языка С в стандарте ANSI с указанием эквивалентных Unicode-функций. 

    ____________________________________Глава 17. Unicode 585 


    char * strcat(char *, const char »): 
    wchar_t * wcscat(wchar_t *, const wchar_t *); 
    char * strchr(const char *, int); 
    wchar_t * wcschr(const wchar_t *, wchar_t); 
    int strcinp( const char *, const char *); 
    int wcsc(np(const wchar_t *, const wcriar_t *); 
    int _stricmp(const char *, const char *); 
    int _wcsicmp(const wchar_t *. const wchar_t *); 
    int strcoll(const char *, const char *); 
    int wcscoll(const wchar_t *, const wchar_t «); 
    int _stricoll(const char *, const char *); 
    int _wcsicoll(cohst wchar_t *, const wchar_t *); 
    char * strcpy(char *, const char *); 
    wchar_t * wcscpy(wchar_t *, const wchar_t *); 
    size_t strcspn(const char *. const char *); 
    size_t wcscspn(const wchar_t *, const wchar_t *); 
    char * _strdup(const char *); 
    wchar_t * _wcsdup(const wchar_t *); 
    size_t strlen(const char *); 
    size_t wcslen(const wchar_t *); 
    char * _strlwr(char *); 
    wchar_t * _wcslwr(wchar_t *); 
    char * strncat(char *, const char *, size_t); 
    wchar_t * wcsncat(wchar_t *, const wchar_t *, size_t); 
    int strncmp(const char *, const char *, size_t); 
    int wcsncmp(const wchar_t *, const wchar_t *, size_t); 
    int _strnicmp(const char *, const char *, size_t); 
    int _wcshicmp(const wchar_t *, const wchar_t *, size_t); 
    char * strncpy(char *, const char *, slze_t); 
    wchar_t * wcsncpy(wchar_t *, const wchar_t *, size_t); 
    char * _strnset(char *, int, size_t); 
    wchar_t * _wcsnset(wchar_t *, wchar_t, size_t); 

    см. след. стр. 

    Рис. 17-1. Строковые функции языка С в стандарте ANSI и их Unicode-аналоги 

    586 _____________________________Windows для профессионалов 


    РИС. 17-1   (продолжение) 

    char * strpbrk(const char *, const char *); 
    wchar_t * wcspbrk(const wchar_t *, const wchar_t *); 
    char « strrchr(const char *, int); 
    wchar_t * wcsrchr(const wchar_t *, wchar_t); 
    char * _strrev(char *),; 
    wchar_t * _wcsrev(wchar_t *); 
    char * _strset(char *, int); 
    wchar_t * _wcsset(wchar_t *, wchar_t): 
    size_t strspn(const char *, const char *); 
    size_t wcsspn(const wchar_t *, const wchar_t *); 
    char * strstr(const char *, const char *); 
    wchar_t * wcsstr(const wchar_t *, const wchar_t *): 
    char * strtok(char *, const char *); 
    wchar_t * wcstok(wchar_t *, const wchar_t *); 
    char * _strupr(char *); 
    wchar_t * _'wcsupr(wchar_t *); 
    size_t strxfrm(char *, const char *, size_t); 
    slze_t wcsxrrm(wchar_t *, const wchar_t *, size_t); 

    Обратите внимание, что имена всех новых функций начинаются с wcs — это 
аббревиатура wide character set (набор широких символов). Таким образом, имена 
Unicode-функций образуются простой заменой префикса str соответствующих 
ANSI-функций на wcs. 
    Код, содержащий явные вызовы str- или м/с5-функций, просто так 
компилировать с использованием и ANSI, и Unicode нельзя. Чтобы реализовать 
возможность компиляции «двойного назначения», замените в своей программе 
заголовочный файл STRING.H на TCHAR.H. Он помогает создавать универсальный 
исходный код, способный задействовать как ANSI, так и Unicode, — и это 
единственное, для чего служит файл TCHAR.H. Он состоит из макросов, заменяющих 
явные вызовы str- или м/с5-функций. Если при компиляции исходного кода Вы 
определяете _UNICODE, макросы ссылаются на wcs-функции, а в его отсутствие — 
на 5<т-функции. На рис. 17-2 приведен список макросов из файла TCHAR.H и 
показано, на что они ссылаются в зависимости от того, определен _UNICODE или нет. 

    Макрос из TCHAR.H        „UNICODE определен         _UNICODE не определен 

    _tprintf                      wprintf                          printf 
    _ftprintf                      fwprintf                         fprintf 
    _stprintf                      swprintf                         sprintf 
    _sntprintf                    _snwprintf                       _snprintf 

    см. след. стр. 

    Рис. 17-2. Макросы в файле TCHAR.H и их ссылки 

    ___________________________________Глава 17. Unicode 587 


    РИС. 17-2 (продолжение) 

    Макрос из TCHAR.H „UNICODE определен „UNICODE не определен 

    _vtprintf vwprintf vprintf 
    _vftprintf vfwprintf vfprintf 
    _vstprintf vswprintf vsprintf 
    _vsntprintf _vsnwprintf _vsnprintf 
    _tscanf wscanf scanf 
    _ftscanf fwscanf fscanf 
    _stscanf swscanf sscanf 
    _fgettc fgetwc fgetc 
    _fgettchar _fgetwchar _fgetchar 
    _fgetts fgetws fgets 
    _fputtc fputwc fputc 
    _fputtchar _fputwchar _fputchar 
    _fputts fputws fputs 
    _gettc getwc getc 
    _getts _getws _gets 
    _puttc putwc putc 
    _putts _putws _puts 
    _ungettc ungetwc ungetc 
    _tcstod wcstod strtod 
    _tcstol wcstol strtol 
    _tcstoul wcstoul strtoul 
    _tcscat wcscat strcat 
    _tcschr wcschr strchr 
    _tcscmp wcscmp strcmp 
    _tcscpy wcscpy strcpy 
    _tcscspn wcscspn strcspn 
    _tcslen wcslen strlen 
    _tcsncat wcsncat strncat 
    _tcsncmp wcsncmp strncmp 
    _tcsncpy wcsncpy strncpy 
    _tcspbrk wcspbrk strpbrk 
    _tcsrchr wcsrchr strrchr 
    _tcsspn wcsspn strspn 
    _tcsstr wcsstr strstr 
    _tcstok wcstok strtok 
    _tcsdup _wcsdup _strdup 
    _tcsicmp _wcsicmp _stricmp 
    _tcsnicmp _wcsnicnip _strnicmp 
    _tcsnset _wcsnset _strnset 

    см. след. стр. 

    588 _____________________________Windows для профессионалов 


    РИС. 17-2   (продолжение) 

    Макрос из TCHAR.H        „UNICODE определен         _UNICODE не определен 

    _tcsrev                         _wcsrev                           _strrev 
    _tcsset                         _wcsset                            _strset 
    _tcslwr                         _wcslwr                           _strlwr 
    _tcsupr                        _wcsupr                           _strupr 
    _tcsxfrm                       wcsxfrm                           strxfrm 
    _tcscoll                        wcscoll                            strcoll 
    _tcsicoll                        _wcsicoll                          _stricoll 
    _istalpha                       iswalpha                           isalpha 
    _istupper                       iswupper                          isupper 
    _istlower                       iswiower                           islower 
    _istdigit                        iswdigit                            isdigit 
    _istxdigit                       iswxdigit                           isxdigit 
    _istspace                       iswspace                           isspace 
    _istpunct                       iswpunct                          ispunct 
    _istalnum                      iswalnum                          isalnum 
    _istprint                       iswprint                           isprint 
    _istgraph                       iswgraph                           isgraph 
    _istcntrl                        iswcntrl                            iscntrl 
    _istascii                        iswascii                            isascii 
    _totupper                      towupper                          toupper 
    _totlower                      towlower                          tolower 

    Используя идентификаторы из левой колонки. Вы напишете программу, которую 
можно компилировать в расчете как на Unicode, так и на ANSI. Но это еще не 
все. В файле TCHAR.H есть дополнительные макросы. 
    Чтобы объявить символьный массив «универсального назначения» 
(ANSI/Unicode), применяют тип данных TCHAR. Если _UNICODE определен, TCHAR 
объявляется так: 

    typedef wchar_t TCHAR; 
    А если _UNICODE не определен, то: 
    typedef char TCHAR; 

    Используя этот тип данных, можно объявить строку символов, как: 

    TCHAR szStnng[100]; 

    Можно также определить указатели на строки: 

    TCHAR «szError = "Error"; 

    Правда, в этом операторе есть одна проблема. По умолчанию компилятор C++ 
фирмы Microsoft транслирует строки как состоящие из символов ANSI, а не 
Unicode. В итоге этот оператор нормально компилируется, если _UNICODE не 
определен, но в ином случае дает ошибку. Чтобы компилятор сгенерировал 
Unicode-, а не ANSI-строку, оператор надо переписать так: 

    TCHAR *szError = L"Error"; 

    ____________________________________Глава 17. Unicode 589 

    Заглавная буква L перед строковым литералом указывает компилятору, что его 
надо обрабатывать как Unicode-строку. Тогда, размещая строку в разделе данных 
программы, компилятор вставит между всеми символами нулевые байты. Но 
возникает другая проблема — программа компилируется, только если _UNICODE 
определен. Следовательно, нужен макрос, способный избирательно ставить L перед 
строковым литералом. Эту работу выполняет макрос _ТЕХТ, содержащийся в файле 
TCHAR.H. Если определен _UNICODE, то _ТЕХТ определяется как: 

    ftdefine _TEXT(x)    L ## x 

    В ином случае _ТЕХТ определяется следующим образом: 

    ftdefine _TEXT(x)    x 

    Используя этот макрос, перепишем злополучный оператор, чтобы его можно 
было корректно компилировать независимо от того, определен _UNICODE или нет: 

    TCHAR *szError = _ТЕХТ("Еггог"); 

    Макрос _ТЕХТ применяется и для символьных литералов. Например, чтобы 
проверить, является ли первый символ строки заглавной буквой /: 

    if (szError[0] == _TEXT('J')) { 
    // первый символ - J 
    } else < 
    // первый символ - не J 

    Типы данных, определенные в Win32 для Unicode 

    В заголовочных файлах Win32 определены такие типы данных: 

    Тип данных     Описание 

    WCHAR       Unicode-символ 
    LPWSTR        Указатель на Unicode-строку 
    LPCWSTR      Указатель на строковую константу в кодировке Unicode 

    Эти типы данных относятся исключительно к символам и строкам в кодировке 
Unicode. В заголовочных файлах Win32 определены также универсальные 
(ANSI/Unicode) типы данных LPTSTR и EPCTSTR, указывающие — в зависимости от 
того, определен ли при компиляции макрос UNICODE, — на ANSI- или на Unicode-строку. 
    Кстати, на этот раз имя макроса UNICODE не предваряется знаком 
подчеркивания. Дело в том, что макрос _UNICODE используется в заголовочных 
файлах стандартной библиотеки С, а макрос UNICODE — в заголовочных файлах 
Win32. Компилируя исходный код своего модуля, обычно приходится определять оба макроса. 
    Unicode- и ANSI-функции в Win32 
    Я уже говорил, что существуют 2 функции CreateWindowEx: одна принимает 
строки в Unicode, другая — в ANSI. Все так, но в действительности прототипы 
этих функций чутьчуть отличаются: 

    590 ___________________________Windows для профессионалов 

    HWND WINAPI CreateWindowExW(DWORD dwExStyle, LPGWSTR IpClassName, 
    LPCWSTR IpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int 
nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hinstance, LPVOID IpParam); 
    HWND WINAPI CreateWindowExA(DWORD dwExStyle, LPCSTR IpClassName. 
    LPCSTR IpWindowName, DWORD dwStyle, int X, int Y, int nWidth. int nHeight, 
HWND hWndParent, HMENU hMenu, HINSTANGE hinstance. LPVOID IpParam): 
    CreateWindowExW — это Unicode-версия. Буква W в конце имени функции — 
аббревиатура слова wide (широкий). Символы Unicode занимают по 16 битов 
каждый, поэтому их иногда называют широкими символами (wide characters). Буква 
А в конце имени CreateWindowExA указывает, что данная версия функции принимает ANSI-строки. 
    Но обычно CreateWindowExW или CreateWindowExA напрямую не вызывают, а 
обращаются к CreateWindowEx — макросу, определенному в файле WINUSER.H: 

    tfifdef UNICODE 
    «define CreateWindowEx CreateWindowExW 
    Seise 
    «define CreateWindowEx CreateWindowExA 
    ftendif // !UNICODE 

    Какая именно версия Create WindowEx будет вызвана, зависит от того, 
определен ли UNICODE в период компиляции. Перенося 16-разрядное 
Windows-приложение в Win32, Вы, вероятно, не станете определять UNICODE. Тогда 
все вызовы CreateWindowEx будут преобразованы в вызовы CreateWindowExA — 
ANSI-версии функции. И перенос приложения упростится, ведь 16-разрядная 
Windows работает только с ANSI-версией CreateWindowEx. 
    В Windows NT функция Create WindowExA — надстройка, которая выделяет 
память для конвертации строк из ANSI в Unicode и вызывает CreateWindowExW, 
передавая ей преобразованные строки. Когда CreateWindowExW вернет управление, 
CreateWindowExA освободит буферы и передаст Вам описатель. 
    Разрабатывая DLL, которую будут использовать и другие программисты, стоит 
предусмотреть в ней 2 точки входа — для ANSI и для Unicode. В ANSI-версии 
просто выделяйте память, преобразуйте строки и вызывайте Unicode-версию той 
или иной функции. (Этот процесс я продемонстрирую позже.) 
    В Windows 95 основную работу выполняет CreateWindowExA. В этой 
операционной системе предусмотрены точки входа для всех Win32^yHKUH^ 
принимающих Unicode-строки, но функции не транслируют их в ANSI, а просто 
сообщают об ошибке. Последующий вызов GetLastError дает 
ERROR_CALL_NOT_IMPLEMENTED. Должным образом действуют только ANSI-версии 
функций. Ваше приложение не будет работать в Windows 95, если в 
скомпилированном коде присутствуют вызовы «широкосимвольных» \\[тЭ2-функций. 
    Некоторые функции Win32 API (например, WinExecnnn OpenFile) существуют 
только для совместимости с 16-разрядными программами, и их надо избегать. 
Лучше заменить все вызовы WinExec и OpenFile вызовами новых — CreateProcess и 
CreateFile. Тем более, что старые функции просто обращаются к новым. 
    Самая серьезная проблема со старыми функциями в том, что они не принимают 
строки в Unicode, — при их вызове Вы должны передавать строки в ANSI. С другой 
стороны, в Windows NT у всех новых или пока не устаревших функций обязательно 
есть как ANSI-, так и Unicode-версия. 

    ____________________________________Глава 17. Unicode 591 


    Программы, способные использовать и ANSI, и Unicode 

    Неплохая мысль — заранее подготовить свое приложение к Unicode, даже если 
Вы пока не планируете работать с этой кодировкой. Вот главное, что для этого нужно: 
    • привыкайте к тому, что текстовые строки — это массивы символов, а не 
массивы байтов или значений типа char, 
    • используйте универсальные типы данных (вроде TCHAR или LPTSTR) для 
символов и текстовых строк; 
    • используйте явные типы данных (вроде BYTE или LPBYTE) для байтов, 
указателей на байты и буферов данных; 
    • применяйте макрос _ТЕХТ для определения символьных и строковых литералов; 
    • предусмотрите возможность глобальных замен (например, LPSTR на LPTSTR); 
    • модифицируйте логику строковой арифметики — например, преобразуйте 
sizeof(szBuffer) в выражение (sizeof(szBuffer) I sizeof (TCHAR)). Помнить об 
этом труднее всего — я сам забывал о нем столько раз, что сбился со счета. 
    Разрабатывая программы-примеры для первого издания книги, я сначала 
написал их так, что они компилировались только с использованием ANSI. Но, 
дойдя до этой главы, понял, что Unicode лучше, и решил написать примеры, 
которые показывали бы, как легко создавать программы, компилируемые с 
применением и Unicode, и ANSI. В конце концов я преобразовал все 
программы-примеры так, чтобы их можно было компилировать в расчете на любую из 
этих систем кодировки символов. 
    Конверсия всех программ заняла примерно 4 часа — неплохо, особенно если 
учесть, что у меня совсем не было опыта в этом деле. 

    Строковые функции в Win32 

    Win32 API предусматривает набор функций для работы с Unicode-строками: 

    Функция    Описание 

    Istrcat       Выполняет конкатенацию строк. 
    Istrcmp      Сравнивает 2 строки с учетом регистра букв. 
    Istrcmpi      Сравнивает 2 строки без учета регистра букв. 
    Istrcpy       Копирует строку в другой участок памяти. 
    Istrlen       Возвращает длину строки в символах. 

    Они реализованы как макросы, вызывающие либо Unicode-, либо ANSI-версию 
функции в зависимости от того, определен ли UNICODE при компиляции исходного 
модуля. Например, если UNICODE не определен, Istrcat раскрывается в IstrcatA, 
определен — в IstrcatW. 
    Строковые \Уш32-функции Istrcmp и Istrcmpi ведут себя не так, как их 
аналоги из стандартной библиотеки С (strcmp, strcmpi, wcscmp и wcscmpi), 
которые сравнивают значения кодовых позиций в символах строк. Игнорируя 
фактические символы, они просто сравнивают числовое значение каждого символа 
первой строки с числовым значением символа второй строки. Но Istrcmp и 
Istrcmpi реализованы через вызовы \Уш32-функции Compares tring: 

    592 _____________________________Windows для профессионалов 

    int CompareString(LCID Icid, DWORD fdwStyle, LPCWSTR IpStringI, int cch1, 
LPCWSTR lpString2, int cch2): 

    Она сравнивает 2 строки в Unicode. Первый параметр задает так называемый 
локализующий идентификатор (locale ID, LCID) — 32-битную величину, 
определяющую конкретный язык. С помощью этого идентификатора CompareString 
сравнивает строки с учетом смысла конкретных символов в данном языке. Так что 
она действует куда осмысленнее, чем функции стандартной библиотеки. 
    Когда любая из функций семейства Istrcmp вызывает CompareString, в первом 
параметре передается результат вызова УУш32-функции GetThreadLocale: 

    LCID GetThreadLocale(VOID); 

    Она возвращает уже упомянутый локализующий идентификатор, который 
присваивается потоку в момент его создания. 
    Второй параметр функции CompareString указывает флаги, модифицирующие 
метод сравнения строк. Вот допустимые флаги: 

    Флаг                        Действие 

    NORM_IGNORECASE          Различия в регистре букв игнорируются. 
    NORM_IGNOREKANATYPE     Различия между знаками хираганы и катаканы игнорируются. 
    NORM_IGNORENONSPACE     Знаки, отличные от пробелов, игнорируются. 
    NORM_IGNORESYMBOLS       Символы, отличные от алфавитно-цифровых, игнорируются. 
    NORM_IGNOREWIDTH         Разница между одно- и двухбайтовым представлением 
одного и того же символа игнорируется. 
    NORM_STRINGSORT           Знаки препинания обрабатываются как и символы, 
отличные от алфавитно-цифровых. 

    Вызывая CompareString, функция Istrcmp передает в параметре fdwStyle нуль, 
a Istrcmpi — флаг NORM_IGNORECASE. Остальные 4 параметра определяют 2 строки и 
их длину. Если cch1 равен -1, функция считает, что строка IpStringI 
завершается нулевым символом, и автоматически вычисляет ее длину. То же 
относится и к параметрам cch2 и lpString2. 
    Другие функции стандартной библиотеки С тоже толком не работают с 
Unicode-строками. Например, функции tolower и toupper неправильно преобразуют 
регистр букв со знаками ударения. Поэтому для Unicode-строк лучше использовать 
соответствующие Win32функции. К тому же они корректно работают и с ANSI-строками. 
    Первые две функции: 

    LPTSTR CharLower(LPTSTR IpszString); 
    LPTSTR CharUpper(LPTSTR IpszString); 

    преобразуют либо отдельный символ, либо целую строку с нулевым символом в 
конце. Чтобы преобразовать всю строку, просто передайте ее адрес. Но, 
преобразуя отдельный символ, поступают иначе: 

    TCHAR cLowerCaseChr = ChatLower((LPTSTR) sz3tring[0]): 

    Приведение типа отдельного символа к LPTSTR вызывает обнуление старших 16 
битов указателя, а в его младшие 16 битов помещается сам символ. Обнаружив, 
что старшие 16 битов указателя равны 0, функция «поймет», что Вы хотите 
преобразовать не строку, а отдельный ее символ. Возвращаемое 32-битное 
значение содержит результат преобразования в младших 16 битах. 

    ____________________________________Глава 17. Unicode 593 

    Следующие две функции аналогичны двум предыдущим за исключением того, что 
они преобразуют символы, содержащиеся в буфере (который не требуется завершать 
нулевым символом): 

    DWORD CharLowerBuff(LPTSTR IpszString, DWORD cchString): 
    DWORD CharUpperBuff(LPTSTR IpszString, DWORD cchString): 

    Прочие функции стандартной библиотеки С (например, isalpha, islower и 
isupper) возвращают значение, которое сообщает, является ли данный символ 
буквой, а также строчная она или прописная. В Win32 тоже есть подобные 
функции, но они способны учитывать и язык, выбранный пользователем в Control Panel: 

    BOOL IsGharAlpha(TCHAR ch); 
    BOOL IsCharAlphaNumeric(TCHAR ch); 
    BOOL IsCharLower(TCHAR ch); 
    BOOL IsCharUpper(TCHAR ch); 

    И последняя группа функций из стандартной библиотеки С, о которых я хотел 
рассказать, — printf. Если при компиляции _UNICODE определен, они ожидают 
передачи всех символьных и строковых параметров в Unicode; в ином случае — в ANSI. 
    В \Уш32-функции wsprintf— расширенной версии sprintfu3 стандартной 
библиотеки — предусмотрены некоторые дополнительные типы полей, позволяющие 
явно указывать кодировку данного символа или строки. Благодаря этому в одном 
вызове wsprintf символы и строки можно указать в смешанной кодировке: как в 
ANSI, так и в Unicode. 

    Ресурсы 

    Компилятор ресурсов генерирует двоичное представление всех ресурсов, 
используемых Вашей программой. Строки в ресурсах (таблицы строк, шаблоны 
диалоговых окон, меню и т. д.) всегда записываются в Unicode. Если в программе 
не определяется макрос UNICODE, Windows 95 и Windows NT сами проводят нужные 
преобразования. Например, если при компиляции исходного модуля UNICODE не 
определен, вызов LoadString на самом деле вызывает функцию LoadStringA, 
которая читает строку из ресурсов и преобразует ее в ANSI. Затем Вашей 
программе возвращается ANSI-представление строки. 

    Текстовые файлы 

    Текстовых файлов в кодировке Unicode пока очень мало. Никакие текстовые 
файлы, поставляемые с операционными системами или другими программными 
продуктами Microsoft, не используют Unicode. Думаю, однако, что эта тенденция 
изменится в будущем — пусть даже отдаленном. И, конечно же, программа Notepad 
в Windows NT позволяет создавать или открывать как Unicode-, так и ANSI-файлы. 
    Многим приложениям, открывающим и обрабатывающим текстовые файлы 
(например, компиляторам), было бы удобнее, если после открытия файла можно 
было бы определить, содержит он символы в ANSI или в Unicode. В Windows NT, 
начиная с версии 3.5, это стало возможным за счет появления функции IsTextUnicode: 
    DWORD IsTextUnicode(CONST LPVOID IpvBuffer, int cb, LPINT IpResult); 
    Проблема с текстовыми файлами в том, что не существует четких и строгих 
правил относительно их содержимого. Это крайне затрудняет определение того, 
содержит файл 

    594 ___________________________Windows для профессионалов 

    символы в ANSI или в Unicode. Поэтому IsTextUnicode применяет набор 
статистических и детерминистских методов, чтобы сделать взвешенное 
предположение о содержимом буфера. Поскольку тут больше алхимии, чем точной 
науки, нет гарантий, что Вы не получите неверные результаты от IsTextUnicode. 
    Первый ее параметр, IpvBuffer, указывает на буфер, подлежащий проверке. 
При этом используется указатель типа void, поскольку неизвестно, в какой 
кодировке данный массив символов. 
    Параметр cb определяет число байтов в буфере, на который указывает 
IpvBuffer. Так как содержимое буфера неизвестно, cb — счетчик именно байтов, а 
не символов. Заметьте: вовсе не обязательно задавать всю длину буфера. Но чем 
больше байтов проанализирует функция, тем больше шансов получить правильный результат. 
    Ну и параметр IpResnh— это адрес целочисленной переменной, которую надо 
инициализировать перед вызовом функции. Ее значение сообщает, какие тесты 
должна провести IsTextUnicode. (Подробнее об этом см. Microsoft Win32 
Programmer's Reference.) Если IpResult равен NULL, функция IsTextUnicode 
делает все проверки. 
    Функция возвращает TRUE, если считает, что буфер содержит текст в Unicode, 
и FALSE — в ином случае. Если через целочисленную переменную, на которую 
указывает параметр IpResult, были запрошены лишь определенные тесты, функция 
(перед возвратом управления) устанавливает ее биты в соответствии с 
результатами этих тестов. 

    VWINDOWS/ 
    В Windows 95 функция IsTextUnicode по сути не реализована и просто 
возвращает FALSE; последующий вызов GetLastError дает код ошибки ERROR_CALL_NOTJMPLEMENTED. 

    Применение функции IsTextUnicode иллюстрирует программа-пример FileRev 
(см. главу 8). 

    Перекодировка строк из Unicode в ANSI и обратно 

    \Уш32-функция MultiByteToWideChar преобразует мультибайтовые символы 
строки в «широкобайтовые»: 

    int MultiByteToWideChar(UINT uCodePage, DWORD dwFlags, 
    LPCSTR IpMultlByteStr, int cchMultiByte, 
    LPWSTR IpWideCharStr, int cchWideChar): 

    Параметр uCodePage задает номер кодовой страницы, связанной с 
мультибайтовой строкой. Параметр dwFlags позволяет влиять на преобразование 
букв с диакритическими знаками. Обычно эти флаги не используются, и dwFlags 
равен 0. Параметр IpMultlByteStr указывает на конвертируемую строку, а 
cchMultiByte определяет ее длину в байтах. Функция самостоятельно определяет 
длину строки, если cchMultiByte равен -1. 
    Unicode-версия строки, полученная в результате преобразования, 
записывается в буфер по адресу, указанному в параметре IpWideCharStr. 
Максимальный размер этого буфера (в символах) задается в параметре 
cchWideChar. Если он равен 0, функция ничего не преобразует, а просто 
возвращает размер буфера, необходимый для сохранения результата 
преобразования. Обычно конверсия мультибайтовой строки в ее Unicode-эквивалент 
проходит так: 
    1. Вызывают MultiByteToWideChar, передавая NULL в параметре IpWideCharStr 
и 0 в параметре cchWideChar. 
    1. Выделяют блок памяти, достаточный для сохранения преобразованной 
строки. Его размер получают из предыдущего вызова MultiByteToWideChar. 

    ___________________________________ Глава 17. Unicode 595 

    3. Снова вызывают MultiByteToWideChar, на этот раз передавая адрес 
выделенного буфера в параметре IpWideCharStr, а размер буфера, полученный при 
первом обращении к этой функции, — в параметре cch WideChar. 
    4. Работают с полученной строкой. 
    5. Освобождают блок памяти, занятый Unicode-строкой. 
    Обратное преобразование выполняет Wn-132-функция WideCharToMultiByte: 

    int WideCharToMultiByte(UINT uCodePage. DWORD dwFlags, 
    LPCWSTR IpWideCharStr, int cchWideChar, 
    LPSTR IpMultiByteStr, int cchMultiByte, 
    LPCSTR IpDefaultChar, LPBOOL IpfUsedDefaultChar); 

    Она очень похожа на MultiByteToWideChar. И опять uCodePage определяет 
кодовую страницу для строки — результата преобразования. Дополнительный 
контроль над процессом преобразования дает параметр dwFlags. Его флаги влияют 
на символы с диакритическими знаками и на символы, которые система не может 
преобразовать. Такой уровень контроля обычно не нужен, и dwFlags 
приравнивается 0. 
    Параметр IpWideCharStr указывает адрес конвертируемой строки, а 
cchWideChar задает ее длину в символах. Функция сама определяет длину исходной 
строки, если cchWideChar равен -1. 
    Мультибайтовый вариант строки, полученный в результате преобразования, 
записывается в буфер, на который указывает параметр IpMultiByteStr. Параметр 
cchMultiByte определяет максимальный размер этого буфера в байтах. Передав 
нулевое значение в cchMultiByte, Вы заставите функцию сообщить размер буфера, 
требуемый для записи результата. Обычно конверсия широкобайтовой строки в 
мультибайтовую проходит в той же последовательности, что и при обратном преобразовании. 
    Очевидно, Вы заметили, что WideCharToMultiByte принимает на 2 параметра 
больше, чем MultiByteToWideChar, это IpDefaultChar и IpfUsedDefaultChar. 
Функция WideCharToMultiByte использует их, только если встречает широкий 
символ, не представленный в кодовой странице, на которую ссылается uCodePage. 
Если его преобразование невозможно, функция берет символ, на который указывает 
IpDefaultChar. Если этот параметр равен NULL (как обычно и бывает), функция 
использует системный символ по умолчанию. Таким символом обычно служит знак 
вопроса, что при операциях с именами файлов очень опасно, ведь он — символ подстановки. 
    Параметр IpfUsedDefaultChar указывает на переменную типа BOOL, которую 
функция устанавливает как TRUE, если хоть один символ из широкосимвольной 
строки не преобразован в свой мультибайтовый эквивалент. А если все символы 
преобразованы успешно, функция устанавливает переменную как FALSE. Обычно Вы 
передаете NULL в этом параметре. 
    Подробнее эти функции и их применение описаны в Microsoft Win32 
Programmer's Reference. 
    Эти две функции позволяют легко создавать ANSI- и Unicode-версии других 
функций, работающих со строками. Например, у Вас есть DLL, содержащая функцию, 
которая переставляет все символы строки в обратном порядке. Unicode-версию 
этой функции можно было бы написать так: 

    BOOL StringReverseW (LPWSTR IpWideCharStr) { 
    // получаем указатель на последний символ в строке 
    LPWSTR IpEndOfStr = IpWideCharStr + wcslen(lpWideCharStr) - 1; 
    wchar_t cCharT; 

    см. след. стр. 

    596 _____________________________Windows для профессионалов 

    // повторяем, пока не дойдем до середины строки 
    while (IpWideCharStr < IpEndOfStr) { 
    // записываем символ во временную переменную 
    cCharT = «IpWideCharStr; 
    // помещаем последний символ на место первого 
    •IpWideCharStr = *lpEndOfStr; 
    // копируем символ из временной переменной на место 
    // последнего символа 
    «IpEndOfStr = cCharT; 
    // продвигаемся на 1 символ вправо 
    !pWideCharStr++ 
    // продвигаемся на 1 символ влево 
    IpEndOfStr—: 
    // строка обращена: сообщаем об успешном завершении 
    return(TRUE); 
    } 

    ANSI-версию этой функции можно написать так, чтобы она вообще ничем не 
занималась, а просто преобразовывала ANSI-строку в Unicode и передавала ее в 
функцию StringReverseW; затем она конвертирует обращенную строку снова в ANSI. 
Тогда функция должна выглядеть примерно так: 

    BOOL StringReverseA (LPSTR IpMultiByteStr) { 
    LPWSTR IpWideCharStr: 
    int nLenOfWideCharStr; 
    BOOL fOk = FALSE; 
    // вычисляем количество символов, необходимых для 
    // хранения широкосимвольной версии строки 
    nLenOfWideCharStr = MultiByteToWideChar(CP_ACP, О, 
    IpMultiByteStr, -1, NULL, О): 
    
    // Выделяем память из стандартной кучи процесса, 
    // достаточную для хранения широкосимвольной строки. 
    // Не забудьте, что MultiByteToWideChar возвращает 
    // количество символов, а не байтов, поэтому мы должны 
    // умножить это число на размер широкого символа. 
    IpWideCharStr = HeapAlloc(GetProcessHeap(), О, 
    nLenOfWideCharStr * sizeof(WCHAR)); 
    if (IpWideCharStr == NULL) 
    return(fOk): 
    
    // преобразуем мультибайтовую строку в широкосимвольную 
    MultiByteToWideChar(CP_ACP, 0, IpMultiByteStr, -1, 
    IpWideCharStr, nLenOfWideCharStr); 


    ___________________________________Глава 17. Unicode 597 


    // вызываем широкосимвольную версию этой функции 
    // для выполнения настоящей работы 
    f0k = StringReverseW(lpWideCharStr); 
    if (f0k) { 
    // преобразуем широкосимвольную строку обратно в мультибайтовую 
    WideCnarToMultiByte(CP_ACP, 0. IpWideCharStr, -1, 
    IpMultiByteStr, strlen(lpMultiByteStr), NULL, NULL); 
    } 
    // освобождаем память, выделенную под широкобайтовую сгроку 
    HeapFree(GetProcessHeap(), 0, IpWideCharStr); 
    return(fOk); 

    И, наконец, в заголовочном файле, поставляемом вместе с DLL, прототипы 
этих функций были бы такими: 

    BOOL StringReverseW (LPWSTR IpWideCharStr): 
    BOOL StringReverseA (LPSTR IpMultiByteStr); 
    Bifdef UNICODE 
    Sdefine StringReverse StringReverseW 
    ftelse 
    ftdefine StringReverse StringReverseA 
    #endif // !UNICODE 


    Windows NT: оконные классы и процедуры 

    Регистрируя новый оконный класс, Вы должны сообщить системе адрес оконной 
процедуры, отвечающей за обработку сообщений для этого класса. В некоторых 
сообщениях (вроде WM_SETTEXT) параметр IParam является указателем на строку. 
Перед отправкой сообщения системе нужно знать, требует ли данная оконная 
процедура для корректной обработки сообщения, чтобы строка была в ANSI или в Unicode. 
    Система определяет это по функции, с помощью которой Вы регистрируете 
оконный класс. Если Вы создаете структуру WNDCLASS и вызываете RegisterClassA, 
она считает, что оконная процедура ожидает все строки и символы в ANSI. А если 
Вы обращаетесь к функции RegisterClassW, значит, оконная процедура требует 
Unicode. Макрос RegisterClass, конечно же, раскрывается либо в RegisterClassA, 
либо в RegisterClassW — в зависимости от того, определен ли UNICODE при 
компиляции исходного модуля. 
    Располагая описателем окна, можно узнать, какого типа символы и строки 
ожидает оконная процедура. Для этого вызовите: 

    BOOL IsWindowUnicode(HWND hwnd); 

    Если оконная процедура указанного окна ожидает Unicode, эта функция 
возвращает TRUE; 
    в ином случае — FALSE. 
    Если Вы создаете строку в ANSI и посылаете сообщение WM_SETTEXT окну, чья 
процедура ожидает Unicode, система автоматически преобразует строку перед 
посылкой сообщения. Так что маловероятно, что функция IsWindowUnicode Вам 
вообще когда-нибудь понадобится. 
    Система осуществляет автоматическую перекодировку и при создании подкласса 
оконной процедуры. Допустим, оконная процедура для поля ввода (edit control) ожидает 

    598 _____________________________Windows для профессионалов 

    строки и символы в Unicode. Затем где-то в программе Вы создаете поле 
ввода и подкласс его оконной процедуры вызовом: 

    LONG SetWindowLongA(HWND hwnd, int nindex, LONG INewLdng); 

    или 

    LONG SetWindowLongW(HWND hwnd, int nindex, LONG INewLong); 

    передавая GWL_WNDPROC в параметре nindex и адрес процедуры подкласса в 
параметре INewLong. А если Ваша процедура подкласса ожидает строки и символы в 
ANSI? Это может вызвать серьезную проблему. Ведь система определяет, как 
преобразовывать строки и символы, по функции, использованной Вами при создании 
подкласса. Вызывая SetWindowLongA, Вы тем самым сообщаете системе, что новая 
оконная процедура (Ваша процедура подкласса) принимает ANSI-строки. И 
фактически, вызвав IsWindowUnicode после SetWindowLongA, Вы увидели бы, что 
она возвращает FALSE, указывая, что оконная процедура нового подкласса поля 
ввода более не принимает Unicode. 
    Но теперь возникает новая проблема: как гарантировать, что исходная 
оконная процедура получит корректный тип строк и символов? Чтобы правильно 
конвертировать строки, системе нужны два вида информации. Первый — в какой 
кодировке строка представлена сейчас. Об этом мы сообщаем ей вызовом 
CallWindowProcA или CallWindowProcW: 

    LRESULT CallwinddwProcA(WNDPROC wndprcPrev, HWND hwnd, 
    UINT uMsg, WPARAM wParam, LPARAM IParam); 
    LRESULT CallWindowProcW(WNDPROC wndprcPrev, HWND hwnd, 
    UINT uMsg, WPARAM wParam, LPARAM IParam): 
    
    Если процедура подкласса хочет передать исходной оконной процедуре 
ANSI-строки, ей нужно вызвать CallWindowProcA. Если же она хочет передать 
Unicode-строки, то — CallWindowProcW. 
    Второй вид информации — тип строк, ожидаемый исходной оконной процедурой. 
Система получает эту информацию по адресу оконной процедуры. Когда Вы 
вызываете SetWindowLongA или SetWindowLongW, система проверяет, задаете ли Вы 
ANSI-процедуру подкласса для Unicode-окна или наоборот. Если ожидаемый тип 
строк не меняется, SetWindowLong просто возвращает адрес исходной оконной 
процедуры. Если же такой тип строк меняется, SetWindowLong вместо этого адреса 
возвращает описатель внутренней структуры \Ут32-подсистемы. 
    Эта структура содержит истинный адрес исходной оконной процедуры и 
значение, указывающее, что ожидает процедура: ANSI или Unicode. Когда Вы 
вызываете CallWindowProc, система проверяет, что Вы передаете: описатель одной 
из внутренних структур данных или фактический адрес оконной процедуры. Если 
передан адрес оконной процедуры, вызывается именно она, и преобразования 
символов или строк не проводятся. Если же Вы передали адрес внутренней 
структуры данных, система, выполнив нужные преобразования, вызывает исходную 
оконную процедуру.