- у исполняемого файла есть манифест с name="Microsoft.Windows.Common-Controls" в <assemblyIdentity>
- в таблице импорта нет ссылок на comctl32.dll
Типичное решение этой проблемы – предварительный вызов InitCommonControlsEx из comctl32.dll. И на самом деле вызов InitCommonControlsEx решает проблему. Самое интересно, что вызов этой функции не обязательно вставлять перед DialogBoxParamW – можно в любом месте программы (и вообще, как будет показано ниже, главное, чтобы в таблице импорта была ссылка на comctl32).
Проведенное расследование показало, что на самом деле вызов InitCommonControlsEx вовсе не обязателен для корректной работы, т.к. эта функция в любом случае вызывается в точке входа comctl32.dll при получении уведомления DLL_PROCESS_ATTACH (ниже приведена часть стека).
comctl32.dll!InitCommonControlsEx comctl32.dll!_ProcessAttach comctl32.dll!LibMain(DLL_PROCESS_ATTACH) ...А корень "проблемы" содержится во внутренней функции VerNtUserCreateWindowEx.
Как оказалось, все, что нужно для корректной работы – чтобы на момент вызова DialogBoxParamW библиотека comctl32 уже была загружена в адресное пространство процесса (речь идет об Windows XP). Этого можно достичь двумя способами:
- вставить в код вызов InitCommonControlsEx (на самом деле можно вызвать любую функцию и в любом месте программы), что приведет к появлению ссылки в таблице импорта и загрузке comctl32 во время инициализации процесса
- предварительно загрузить comctl32.dll динамически – LoadLibrary("comctl32.dll") (такой себе "костыль")
Для примера рассмотрим следующий шаблон диалогового окна
TESTDLG DIALOGEX 0, 0, 330, 115 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU EXSTYLE WS_EX_DLGMODALFRAME | WS_EX_CONTROLPARENT CAPTION "Test Program" LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US FONT 8, "Ms Shell Dlg 2" { CONTROL "Label Caption", 1001, STATIC, SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_GROUP, 65, 15, 194, 14 }
Создадим тестовый проект, не содержащий статических ссылок на comctl32.dll, и рассмотрим, что же происходит "под капотом".
Тестовое приложение вызываем функцию DialogBoxParamW (comctl32.dll при старте в память не загружается)
- Вызывается VerNtUserCreateWindowEx для создания окна "Test Program"
Внутри происходит вызов NtUserCreateWindowEx, который завершается успешно
- Вызывается VerNtUserCreateWindowEx для создания окна "Label Caption" (STATIC CONTROL)
Внутри происходит вызов NtUserCreateWindowEx, который завершается с ошибкой LastError = 1407 ("Cannot find window class").
Что собственное и не удивительно, т.к. comctl32.dll не загружалась и, соответственно, никакие классы не регистрировала.
На момент вызова VerNtUserCreateWindowEx стек выглядит примерно так
user32!VerNtUserCreateWindowEx user32.dll!InternalCreateDialog user32.dll!InternalDialogBox user32.dll!DialogBoxIndirectParamAorW user32.dll!DialogBoxParamW ...
Управления возвращается в InternalCreateDialog и, по цепочке, в – DialogBoxParamW без отображения окна и какого либо признака ошибки.
После возврата управления в InternalCreateDialog код последней ошибки = 1411 ("Class does not exist") – такая ошибка получается в точке входа shlwapi при обработке DLL_PROCESS_DETACH в результате дерегистрации классов "WorkerA", "WorkerW" (SHUnregisterClassesA). Ошибка эта возвращается функцией user32!GetClassInfoA
Есть неподтвержденная информация, будто бы в старых версиях MSDN утверждается "Windows XP: If a manifest is used, InitCommonControlsEx is not required". В новых редакциях такого комментария нет.
В Windows Vista+ "ошибка" исправлена.
Теперь VerNtUserCreateWindowEx вызывает внутреннюю функцию VersionRegisterClass, которая также загружает comctl32 динамически и пытается найти адрес процедуры "RegisterClassNameW".
GetProcAddress отрабатывает успешно (в отличие от предыдущих систем), после чего вызывается RegisterClassNameW("Static").
А вызов InitCommonControlsEx из comctl32!DllMain теперь стал условным:
...
if IsRunningIn16BitProcess() then
InitCommonControlsEx(...)
...
Ссылки
[1] If InitCommonControls doesn't do anything, why do you have to call it?
Комментариев нет:
Отправить комментарий