Сообщений 6    Оценка 175        Оценить  
Система Orphus

Unicode и Windows9x/Me

Автор: Павел Блудов
The RSDN Group
Опубликовано: 22.04.2002
Исправлено: 13.03.2005
Версия текста: 1.0.1

Лирическое вступление
Библиотека Unicows
Первая программа с использованием unicows
Как устроена unicows
Альтернативное использование unicows
Проблемные API
Заключение
Ссылки по теме
"Nothing is impossible!"
Professor Hubert J. Farnsworth

Демонстрационный проект Unicode98 (ATL ActiveX, 32k)
Демонстрационный проект Unicode98b (WTL, 22k)
Файл Unicows.cpp для альтернативной загрузки unicows.dll (2k)

Лирическое вступление

Однажды, в пятницу вечером, я получил письмо, из которого следовало, что заказчик очень хочет, чтобы проект, над которым я работаю, мог бы быть запущен из Windows98.

"А, может, Вам еще и поддержку Microsoft ® Windows ™ версии 2.0 подавай?", - подумал я, но, тем не менее, решил попытаться. Первое, что пришло мне на ум, это просто перекомпилировать все 30 модулей, из которых состоит проект, без директивы препроцессора _UNICODE. Идея здравая, но пришла в мою голову с опозданием месяцев в 6. К сожалению, некоторые из разработчиков, участвовавших в этом проекте, не понимают, как выяснилось, разницу между LPCTSTR и LPCWSTR. Некоторые даже умудрились использовать LPCTSTR при описании интерфейсов! Представляете, что получится, если код, реализующий некий интерфейс, трактует строки как двухбайтовые, а код, пользующийся этим интерфейсом, считает их однобайтовыми? Дело сильно осложнилось наличием нескольких библиотек, исходным кодом которых я не располагал, и, как следствие, не мог их пересобрать. Возможно, что если бы я располагал двумя-тремя неделями, я бы расставил по коду бесконечное количество перекладываний из пустого в порожнее и наоборот, но, увы. Времени у меня было в обрез, и я начал искать другой путь. Настало время разобраться с давным-давно вышедшей библиотекой поддержки уникода для Windows9x/Me.

Библиотека Unicows

Идея, реализованная в этой библиотеке, довольно проста: весь API, рассчитанный на двухбайтовые строки, эмулируется специальными заглушками, преобразующими все строковые параметры из двухбайтовых в однобайтовые, затем вызывается реализованная в Windows9x/Me неуникодная функция, а результат снова перекладывается в двухбайтовые строки. Именно таким образом в WindowsNT реализована поддержка "старого", неуникодного API. В этой системе однобайтовые строки превращаются в двухбайтовые, вызывается соответствующая функция, а результат снова урезают до однобайтовых строк. Странно, что подобный механизм не был встроен в Windows9x/Me изначально. Unicows.dll занимает всего 200k и реализует почти 500 заглушек для работы с двухбайтовыми строками. Давайте попробуем эту замечательную библиотеку.

Первая программа с использованием unicows

Сначала создадим простой ATL проект и добавим в него ActiveX контрол. Теперь добавим поддержку уникода для Windows9x/Me. Процедура "прикручивания" unicows довольно сложная и интуитивно-непонятная. Но сводится она к тому, чтобы добавить Unicows.lib к списку прочих библиотек, причем непременно в самое начало:


Плюс нужно добавить вот такую строку куда-нибудь в StdAfx.cpp:

#pragma comment(linker, "/nod:kernel32.lib /nod:advapi32.lib /nod:user32.lib /nod:gdi32.lib /nod:shell32.lib /nod:comdlg32.lib /nod:version.lib /nod:mpr.lib /nod:rasapi32.lib /nod:winmm.lib /nod:winspool.lib /nod:vfw32.lib /nod:secur32.lib /nod:oleacc.lib /nod:oledlg.lib /nod:sensapi.lib")

Компилируем, запускаем и... не работает! А как же! Я ведь совершенно забыл, что сначала нужно скопировать unicows.dll в системный каталог Windows. Инсталлируем unicows, запускаем... работает! Но, к сожалению, только Debug-версия. Release-версия никак не может создать окно для контрола. Небольшое расследование показало, что в этом виноват макрос _ATL_DLL, из-за которого CWindowImpl::Create вызывает функцию AtlModuleRegisterWndClassInfoW из ATL.DLL, а та, в свою очередь, обращается напрямую к RegisterClassExW из USER32.DLL. Вызов не попадает в unicows, потому что ATL.DLL ничего про нее не знает. Unicows подменяет вызовы только в тех модулях, в сборке которых участвовала unicows.lib

ПРИМЕЧАНИЕ

Майкрософт рекомендует устанавливать unicows.dll не в системный каталог windows, а в "C:\Program Files\Common Files\Microsoft Shared\MSLU\"

Проблема решается простым отключением _ATL_DLL. Это будет стоить всего лишь в 10к, на которые "подрастет" наш модуль. Если Вас не пугает необходимость статически линковать все необходимые библиотеки (типа MFC), можете дальше не читать. Впрочем, у Вас есть хорошая возможность расширить немного свой кругозор.

Итак, продолжим.

Как устроена unicows

Возникает вполне уместный вопрос: "А каким образом это все устроено"? Очень просто. Хитрые манипуляции с unicows.lib необходимы для того, чтобы подменить уникодные функции из модулей kernel32, advapi32, user32, gdi32, shell32, comdlg32, version, mpr, rasapi32, winmm, winspool, vfw32, secur32, oleacc, oledlg и sensapi на функции с аналогичными именами из unicows. А все функции из unicows.lib выглядят примерно так:

UNICOWSAPI ATOM WINAPI user32_RegisterClassExW_Thunk(IN CONST WNDCLASSEXW *lpwcx)
{
    ResolveThunk("user32", "RegisterClassExW", RegisterClassExW, Unicows_RegisterClassExW, GodotFailRegisterClassExW);
    RegisterClassExW(lpwcx);
}

Пусть Вас не пугает странное название этой функции. В .def файле она переименовывается просто в RegisterClassExW, который и находит линковщик.

ATOM WINAPI Unicows_RegisterClassExW (IN CONST WNDCLASSEXW *lpwcx)
{
    // Делаем, что хотим
}

Тогда линковщик, найдя две разных функции, но с одинаковыми именами, отдаст предпочтение той, что находится в нашей программе.

До вызова ResolveThunk, значение RegisterClassExW совпадает с user32_RegisterClassExW_Thunk, а внутри этого вызова происходит изменение этого указателя на функцию из USER32, для WindowsNT/2k/XP, либо на Unicows_RegisterClassExW, для Windows9x/Me с установленной unicows.dll, либо на GodotFailRegisterClassExW, для Windows9x/Me без unicows.dll. В любом случае, user32_RegisterClassExW_Thunk уже не будет никогда вызываться. Первый и последний вызов user32_RegisterClassExW_Thunk был осуществлен через указатель на эту функцию – RegisterClassExW, и значение по этому адресу было исправлено посредством вызова ResolveThunk.

Интересно, что в случае WindowsNT/2k/XP загрузки unicows.dll в память не происходит. ResolveThunk и функции-заглушки находятся в нашей программе. Фактически, осуществляется отложенное связывание функций. Это означает, что никакой лишней работы в случае с уникодной версией Windows не будет. Unicows работает "прозрачно" в этих ОС. В Windows9x/Me имеет место небольшая задержка для инициализации unicows, не заметная, впрочем, на фоне общей "задумчивости" этих ОС.

Альтернативное использование unicows

У стандартного механизма подключения unicows есть большой недостаток: он бесполезен, если имеются уже готовые модули в виде DLL, а не в виде исходных файлов. Помните AtlModuleRegisterWndClassInfoW и как с ней пришлось бороться? Так вот, есть способ лучше. Можно загрузить DLL в память и поправить ее таблицу импорта (см Форматы РЕ и COFF объектных файлов).

Для этого придется просмотреть все импортируемые функции и, если функция с таким же именем присутствует в unicows.dll, переправить обращение к этой функции в unicows.dll. Напишем две функции. Первая загружает в память процесса unicows.dll и настраивает таблицы экспортируемых функций. Вторая исправляет таблицу импорта указанного модуля.

Листинг №1 Перенаправление функций в unicows.dll

С помощью такого кода мы можем не только переправить "на ходу" вызовы уникодных функций из нашего модуля, но и из любого чужого, например ATL.DLL.

Проблема запаздывания

К сожалению, Windows, при загрузке DLL, автоматически вызывает функцию DllMain() с параметром DLL_PROCESS_ATTACH и этот вызов произойдет до того, как мы сможем поправить таблицу импорта этой DLL. В случае с ATL.DLL ничего страшного не происходит, версия этой библиотеки для Windows9x/Me не уникодная, но экспортирует несколько уникодных функций. Для истинно уникодных DLL, к исходному коду которых нет доступа, у меня сработал такой трюк: сначала я вручную вызывал DllMain с параметром DLL_PROCESS_DETACH, затем настраивал таблицу импорта и снова вызывал DllMain, но уже с параметром DLL_PROCESS_ATTACH. К счастью, хотя эти DLL и не могли как следует проинициализироваться при первом вызове DllMain загрузчиком Windows, они делали это молча. Не выдавая пугающих сообщений об ошибках. Возможно, что Вам повезет меньше. Тогда путь только один: писать свой собственный загрузчик, загружающий нужный модуль в память, настраивающий все нужные таблицы, поддержку уникода и лишь потом вызывающий DllMain. Интересно, что ::LoadLibraryEx() умеет загружать модули в память не вызывая DllMain, но... только в WindowsNT/2k/XP! Windows9x/Me флаг DONT_RESOLVE_DLL_REFERENCES не поддерживает. Уникодная версия atl.dll, например, выдает страшное предупреждение и возвращает FALSE в DllMain, завершая работу приложения.

Подобная, но, к счастью, разрешимая проблема имеется и для EXE приложений. Дело в том, что выполнение программы начинается не с WinMain, а с некоторой функции, инициализирующей CRT, а затем уже вызывающей WinMain. Подробнее здесь. Все, что нам нужно, это задать свою точку входа в программу:


и написать эту самую _UnicowsEntry:

Листинг №2 Собственная точка входа в программу

Проблема отложенной загрузки

Отложенная загрузка (подробнее) означает, что некоторые функции не присутствуют в таблице импорта, а связываются с нужными модулями по мере необходимости. Этот механизм очень похож на тот, который реализуют заглушки unicows.

Как таковой, проблемы не возникает. Все, что нам нужно – это доработать маленько наши функции и заодно подменить ::GetProcAddress() на нашу собственную функцию, а там мы сначала поищем в unicows.dll, а если не найдем нужной функции, то отправим вызов в настоящую ::GetProcAddress().

Листинг №3 Подмена ::GetProcAddress()

Проблемные API

Некоторые функции не имеют заглушек для уникодной версии в Windows9x/Me. Такими функциям, например, являются ::GetAltTabInfo() и ::RealGetWindowClass(). USER32.DLL из WindowsNT/2k/XP экспортирует по три функции для каждой из них, например, GetAltTabInfo, GetAltTabInfoA и GetAltTabInfoW. В USER32.DLL из Windows98 есть только GetAltTabInfo. USER32.DLL из Windows95 не имеет такой функции вообще. Интересно, что в WinUser.h определены именно GetAltTabInfoA и GetAltTabInfoW, таким образом, даже если ваше приложение скомпилировано без поддержки уникода, Windows9x все равно не сможет его загрузить. Тем не менее, эти функции есть в unicows.dll, и, если воспользоваться стандартным способом подключения unicows, приложение будет работать. Для альтернативного способа нам придется воспользоваться явным (GetProcAddress) или отложенным (delayload) связыванием. Оба пути приведут нас, в конце концов, в unicows.dll, где имеются уникодные версии этих функций.

С Module32First/Module32Next, Process32First/Process32Next из KERNEL32.DLL похожая ситуация. В WindowsNT/2k/XP есть Module32First и Module32FirstW, в Widows98 только Module32First. В Unicows.dll Module32FirstW также отсутствует. Для этих функций, впрочем, можно легко отказаться от уникода. Для этого можно просто отменить макрос UNICODE перед включением TlHelp32.h, а затем включить его обратно.

#ifdef UNICODE
    #undef UNICODE
    #include <Tlhelp32.h>
    #define UNICODE
#else
    #include <Tlhelp32.h>
#endif

Заключение

На прощание хочу обратить Ваше внимание на тот факт, что unicows не обеспечивает настоящей поддержки уникода под Windows9x/Me, Вам не удастся вывести одновременно японские и русские буквы, как в WindowsNT/2k/XP, но позволяет не компилировать и отлаживать две версии одной программы: с уникодом и без. В любом случае, если вам нужны одновременно и китайские и арабские буквы, обойтись без WindowsNT/2k/XP не получится.

Ссылки по теме

Что такое Unicode?

Microsoft Layer for Unicode on Windows 95/98/Me Systems

Q259403 (загрузка Atl.dll с сайта Майкрософт)

Unicows.dll version 1.0

Atl.dll version 3.0


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 6    Оценка 175        Оценить