ПРОГРАММИРОВАНИЕ    НА    V I S U A L   C + +
РАССЫЛКА САЙТА       
RSDN.RU  

    Выпуск No. 71 от 7 апреля 2002 г.

РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN , НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.

Здравствуйте, уважаемые подписчики!


 СТАТЬЯ

Unicode и Windows9x/Me

Автор: Paul Bludov
"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 к списку прочих библиотек, причем непременно в самое начало:

Рис. 1 Настройки проекта

Плюс нужно добавить вот такую строку куда-нибудь в 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, который и находит линковщик.

  • Первые два параметра этой функции – это имя модуля и имя функции, которую подменяет эта заглушка.
  • Третий параметр – указатель на функцию, куда будет помещен либо адрес настоящей функции из USER32, если наша программа выполняется в уникодной версии Windows, либо четвертый или пятый параметр, передаваемый в ResolveThunk.
  • Четвертый параметр – указатель не функцию, эмулирующую RegisterClassExW для неуникодной версии Windows. Код этой функции живет в unicows.dll, но может быть заменен на наш собственный. Для этого в нашей программе необходимо определить вот такую функцию:


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

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

  • Последний параметр задает указатель на функцию, используемую в том случае, если попытка загрузить unicows.dll не увенчалась успехом. Она просто вызывает ::SetLastError(ERROR_CALL_NOT_IMPLEMENTED). Т.е. ведет себя точно так же, как и "родные" заглушки из Windows9x/Me.

До вызова 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


#define MakePtr(cast, base, offset) (cast)((DWORD_PTR)(base) + (DWORD_PTR)(offset))

//@//////////////////////////////////////////////////////////////////////////
// Глобальные переменные

static BOOL     g_bUnicodeOS = FALSE;
static HMODULE  g_hModuleUnicows = NULL;
static LPWORD   g_pdwOrd = NULL;
static LPDWORD  g_pdwAddrs = NULL;
static LPDWORD  g_pdwNames = NULL;
static DWORD    g_dwNames = 0;

//@//////////////////////////////////////////////////////////////////////////
// Вспомогательная функция, возвращает адрес функции из unicows.dll
// с указанным именем, если такая имеется

static LPDWORD GetFunctionAddress(LPCSTR azName)
{
    for (DWORD dwName = 0; dwName < g_dwNames; dwName++)
    {
        if (0 == ::lstrcmpiA(MakePtr(LPSTR, g_hModuleUnicows, g_pdwNames[dwName]), azName))
            return MakePtr(LPDWORD, g_hModuleUnicows, g_pdwAddrs[g_pdwOrd[dwName]]);
    }

    return NULL;
}

//@//////////////////////////////////////////////////////////////////////////
// Вспомогательная функция, возвращает имя функции по ее номеру
// для указанного модуля

static LPCSTR GetNameFromOrdinal(HANDLE hModule, DWORD dwOrdinal)
{
    if (!hModule)
        return FALSE; // Нет такого модуля
    
    // Находим таблицу экспорта
    PIMAGE_DOS_HEADER pDosHeader = PIMAGE_DOS_HEADER(hModule);

    if (::IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER))
            || IMAGE_DOS_SIGNATURE != pDosHeader->e_magic)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    PIMAGE_NT_HEADERS pNTHeaders =
        MakePtr(PIMAGE_NT_HEADERS, hModule, pDosHeader->e_lfanew);
    
    if (::IsBadReadPtr(pNTHeaders, sizeof(IMAGE_NT_HEADERS))
            || IMAGE_NT_SIGNATURE != pNTHeaders->Signature)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    IMAGE_DATA_DIRECTORY& expDir =
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    PIMAGE_EXPORT_DIRECTORY pExpDir =
        MakePtr(PIMAGE_EXPORT_DIRECTORY, hModule, expDir.VirtualAddress);

    LPWORD pdwOrd = MakePtr(LPWORD, hModule, pExpDir->AddressOfNameOrdinals);
    LPDWORD pdwNames = MakePtr(LPDWORD, hModule, pExpDir->AddressOfNames);
    dwOrdinal -= pExpDir->Base;
    for (DWORD iName = 0; iName < pExpDir->NumberOfNames; iName++)
    {
        // Проверяем все номера по порядку
        if (dwOrdinal == pdwOrd[iName])
            return MakePtr(LPSTR, hModule, pdwNames[iName]);
    }
    return NULL;
}
//@//////////////////////////////////////////////////////////////////////////
// Функция инициализации unicows.dll

BOOL _UnicowsInit()
{
    if (0 == (0x80000000 & ::GetVersion()))
    {
        // WinNT/2k/XP: Тут нам делать нечего
        g_bUnicodeOS = TRUE;
        return ::SetLastError(0), TRUE;
    }    
    g_hModuleUnicows = ::LoadLibraryA("Unicows.dll");
    if (!g_hModuleUnicows)
        return FALSE; // Ошибка при загрузке Unicows.dll

    // Находим таблицу экспорта
    PIMAGE_DOS_HEADER pDosHeader = PIMAGE_DOS_HEADER(g_hModuleUnicows);

    if (::IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER))
            || IMAGE_DOS_SIGNATURE != pDosHeader->e_magic)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    PIMAGE_NT_HEADERS pNTHeaders =
        MakePtr(PIMAGE_NT_HEADERS, g_hModuleUnicows, pDosHeader->e_lfanew);
    
    if (::IsBadReadPtr(pNTHeaders, sizeof(IMAGE_NT_HEADERS))
            || IMAGE_NT_SIGNATURE != pNTHeaders->Signature)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    IMAGE_DATA_DIRECTORY& expDir =
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
    PIMAGE_EXPORT_DIRECTORY pExpDir =
        MakePtr(PIMAGE_EXPORT_DIRECTORY, g_hModuleUnicows, expDir.VirtualAddress);

    // Запонимаем таблицу имен и адресов
    g_pdwOrd = MakePtr(LPWORD, g_hModuleUnicows, pExpDir->AddressOfNameOrdinals);
    g_pdwNames = MakePtr(LPDWORD, g_hModuleUnicows, pExpDir->AddressOfNames);
    g_pdwAddrs = MakePtr(LPDWORD, g_hModuleUnicows, pExpDir->AddressOfFunctions);
    g_dwNames = pExpDir->NumberOfNames;

    return TRUE;
}

//@//////////////////////////////////////////////////////////////////////////
// Функция перенаправляющая вызовы в unicows.dll

BOOL _UnicowsRebindImports(HMODULE hModule)
{
    if (g_bUnicodeOS)
        return ::SetLastError(0), FALSE;

    // Находим таблицу импорта
    PIMAGE_DOS_HEADER pDosHeader = PIMAGE_DOS_HEADER(hModule);

    if (::IsBadReadPtr(pDosHeader, sizeof(IMAGE_DOS_HEADER))
            || IMAGE_DOS_SIGNATURE != pDosHeader->e_magic)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    PIMAGE_NT_HEADERS pNTHeaders = 
        MakePtr(PIMAGE_NT_HEADERS, hModule, pDosHeader->e_lfanew);

    if (::IsBadReadPtr(pNTHeaders, sizeof(IMAGE_NT_HEADERS))
            || IMAGE_NT_SIGNATURE != pNTHeaders->Signature)
        return ::SetLastError(ERROR_INVALID_PARAMETER), FALSE;

    IMAGE_DATA_DIRECTORY& impDir = 
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    PIMAGE_IMPORT_DESCRIPTOR pImpDesc = 
        MakePtr(PIMAGE_IMPORT_DESCRIPTOR, hModule, impDir.VirtualAddress),
            pEnd = pImpDesc + impDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR) - 1;

    while(pImpDesc < pEnd)
    {
        // Для каждого импортируемого модуля
        if (pImpDesc->OriginalFirstThunk)
        {
            PIMAGE_THUNK_DATA pNamesTable =
                MakePtr(PIMAGE_THUNK_DATA, hModule, pImpDesc->OriginalFirstThunk);

            while(pNamesTable->u1.AddressOfData)
            {
                LPCSTR azFunctionName;
                if (IMAGE_SNAP_BY_ORDINAL(pNamesTable->u1.Ordinal))
                {
                    // Функция импортируется по номеру, вычисляем имя
                    azFunctionName = GetNameFromOrdinal(
                        ::GetModuleHandleA(MakePtr(LPSTR, hModule, pImpDesc->Name))
                        , IMAGE_ORDINAL(pNamesTable->u1.Ordinal));

                    if (!azFunctionName)
                        continue;
                }
                else
                {
                    PIMAGE_IMPORT_BY_NAME pName = MakePtr(PIMAGE_IMPORT_BY_NAME,
 
                hModule,
                    pNamesTable->u1.AddressOfData); azFunctionName = LPCSTR(pName->Name);
                                        } LPDWORD

                    dwAddr = 
                
                    GetFunctionAddress(azFunctionName); <  
                                         

                    BR  
                

                 >                                  if (dwAddr)
                {
                    // Эта функция есть в Unicows.dll
                    LPDWORD *pProc = MakePtr(LPDWORD *, pNamesTable,
                                pImpDesc->FirstThunk - pImpDesc->OriginalFirstThunk);

                    // Возможно, кто-то это уже сделал
                    if (*pProc != dwAddr)
                    {
                        // Отключаем защиту
                        DWORD dwOldProtect = 0;
                        if (::VirtualProtect(pProc, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect))
                        {
                            *pProc = dwAddr;
                            // Восстанавливаем защиту
                            ::VirtualProtect(pProc, sizeof(DWORD), dwOldProtect, &dwOldProtect);
                        }
                    }
                }
            }
        }
        pImpDesc++;
    }

    return TRUE;
}

С помощью такого кода мы можем не только переправить "на ходу" вызовы уникодных функций из нашего модуля, но и из любого чужого, например 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. Подробнее здесь. Все, что нам нужно, это задать свою точку входа в программу:

Рис. 2 Задание точки входа

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

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


extern "C" void _UnicowsEntry()
{
    if (_UnicowsInit())
    {
        _UnicowsRebindImports(::GetModuleHandleA(NULL));
        _UnicowsRebindImports(::GetModuleHandleA("ATL"));
    }
    else
    {
        ::MessageBox(NULL, _T("Ошибка инициализации UNICOWS.DLL"), NULL, MB_OK | MB_ICONSTOP);
        ::ExitProcess(-1);
    }

#ifdef _UNICODE
    void wWinMainCRTStartup();
    wWinMainCRTStartup();
#else // _UNICODE
    void WinMainCRTStartup();
    WinMainCRTStartup();
#endif // _UNICODE
}

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

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

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

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


static FARPROC WINAPI _UnicowsGetProcAddress(IN HMODULE hModule, IN LPCSTR azName);
static FARPROC (WINAPI *_RealGetProcAddress)(IN HMODULE hModule, IN LPCSTR azName);

static LPDWORD GetFunctionAddress(LPCSTR azName)
{
    if (0 == ::lstrcmpiA("GetProcAddress", azName))
        return LPDWORD(_UnicowsGetProcAddress);

    // Unicows
    for (DWORD dwName = 0; dwName < g_dwNames; dwName++)
    {
        if (0 == ::lstrcmpiA(MakePtr(LPSTR, g_hModuleUnicows, g_pdwNames[dwName]), azName))
            return MakePtr(LPDWORD, g_hModuleUnicows, g_pdwAddrs[g_pdwOrd[dwName]]);
    }

    return NULL;
}
static FARPROC WINAPI _UnicowsGetProcAddress(IN HMODULE hModule, IN LPCSTR azName)
{
    if (IS_INTRESOURCE(azName))
        azName = GetNameFromOrdinal(hModule, WORD(azName));
    
    LPDWORD dwAddr = GetFunctionAddress(azName);
    if (dwAddr)
        return FARPROC(dwAddr);
    return _RealGetProcAddress(hModule, azName);
}

Проблемные 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


 ВОПРОС - ОТВЕТ 

Как реализовать диалог с фоновым изображением?

Автор: Сергей Пиманов

Демонстрационный проект BkDlg

Чтобы реализовать диалог с фоновым изображением, необходимо проделать следующие шаги.

Сначала добавляем член-переменные в класс диалога. Они понадобятся нам в дальнейшем.

CDC     m_dcTemp;
CBitmap m_cBmp;
UINT    m_iBmpHeight;
UINT    m_iBmpWidth;

Битмап, содержащий фоновое изображение, можно загружать как из файла, так и из ресурсов. Во втором случае необходимо добавить в ресурсы соответствующую картинку (например, с идентификатором IDB_BITMAP1).

Далее загружаем картинку в обработчике OnInitDialog(). Для случая с внешним файлом пишем:

m_hBmp=(HBITMAP)
LoadImage(0,"picture.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE);
m_cBmp.Attach(hBmp);

В случае с картинкой из ресурсов:

m_cBmp.LoadBitmap(IDB_BITMAP1);

Затем для обоих случаев:

BITMAP Bmp;
CClientDC dc(this);
m_dcTemp.CreateCompatibleDC(&dc); 
m_dcTemp.SelectObject(m_cBmp);
m_cBmp.GetBitmap(&Bmp);
m_iBmpHeight=Bmp.bmHeight;
m_iBmpWidth=Bmp.bmWidth;

Теперь создаём обработчик сообщения WM_ERASEBKGND, в котором наша картинка будет отрисовываться. Чтобы добавить этот обработчик с помощью ClassWizard, необходимо предварительно переключиться на вкладку Class Info и установить для класса диалога Message filter: Window.

BOOL CTestDlg::OnEraseBkgnd(CDC* pDC) 
{
    RECT rect;
    GetClientRect(&rect);

    int CX=rect.right/m_iBmpWidth;
    int CY=rect.bottom/m_iBmpHeight;

    int Y=0;
    for (int i=0;i<=CY;i++)
    {
        int X=0;
        for(int j=0;j<=CX;j++)
        {

            pDC->BitBlt(X,Y,X+m_iBmpWidth,Y+m_iBmpHeight,&m_dcTemp,0,0,SRCCOPY);
            X+=m_iBmpWidth;
        }

        Y+=m_iBmpHeight;
    }

    return TRUE;
}

Если битмап меньше окна, то он будет размножен (черепица). Возможно реализовать и другие варианты отображения картинки. Кроме черепицы, ещё два из них (центрирование и "растягивание" картинки по размеру окна) реализовано в демонстрационной программе BkDlg.

Вот и всё. Когда битмап больше не нужен, его следует удалить, но об этом за нас позаботится деструктор класса CBitmap.


Это все на сегодня. Пока!

Алекс Jenter   jenter@rsdn.ru
Duisburg, 2001.    Публикуемые в рассылке материалы принадлежат сайту RSDN.

| Предыдущие выпуски     | Статистика рассылки