10 октября 2006

XP Themes and Windows 2000

В текущем у меня проекте активно используются XP-темы и для простоты их использования я создал небольшую иерархию классов:
CSkin - CBaseSkin - CXPThemeSkin

CSkin представляет из себя интерфейс, CBaseSkin рисует без XP-тем (то есть вполне должен выполняться на Windows 2000), CXPThemeSkin соответственно рисует с помощью XP-тем (если не удается - рисует с помощью своего базового класса - CBaseSkin).

CXPThemeSkin использует всякие OpenThemeData(), CloseThemeData() из uxtheme.dll, CBaseSkin ничего этого не использует.

При старте программа определяет версию Windows и при WinXP создает объект CXPThemeSkin, в противном случае - CBaseSkin.

И я наивно полагал, что это будет работать на Windows 2000. А вчера поставил Win2k на виртуальную машину и... разочаровался - при старте приложение вылетало с ошибкой "Unable to find uxtheme.dll". Самое интересное, что дело не доходило даже до WinMain() и создания объекта CxxxSkin. То есть XP-темы еще не использовались, а приложение уже вылетало.

При разборе полетов выяснил, что любое упоминание в программе функций из uxtheme.dll приводит к ее загрузке uxtheme.dll, даже такой безобидный код:
void foo()
{
HTHEME Theme = OpenThemeData(NULL, NULL);
}

void WindMain(...)
{
return 0; // do not call foo()
}

Я уж было предполагал, что придется отказаться от простых вызовов OpenThemeData() и подобных, и переписать весь код с использованием LoadLibrary("uxtheme.dll"), вручную получением указателей на функции типа OpenThemeData().

Оказалось, все гораздо проще: достаточно указать в настройках проекта
Linker/Input/Delay Loaded DLLs: uxtheme.dll

5 комментариев:

Not a kernel guy комментирует...

> При разборе полетов выяснил,
> что любое упоминание в программе
> функций из uxtheme.dll приводит
> к ее загрузке uxtheme.dll

Это, как говориться, by design. Этим как раз статическая линковка от динамической и отличается.

Raider комментирует...

> Этим как раз статическая линковка от динамической и отличается.

Но ведь не обязательно же грузить все библиотеки при старте программы. Можно и потом сделать LoadLibrary()

Not a kernel guy комментирует...

> Но ведь не обязательно же
> грузить все библиотеки при
> старте программы.

Теоретически это так, но на практике всё несколько сложнее. Просматривая список статически прилинкованных библиотек NT Loader просто обязан загрузить каждую из них. Этому есть две причины: во-первых у него не будет никаких шансов загрузить эту dll по требованию, поскольку вызов функции из dll мало чем отличается от простого вызова функции. Loader об этом вызове никак не уведомляется. Во-вторых код в DllMain может иметь скрытые зависимости, о которых Loader так же не подозревает.

С другой стороны отложенная загрузка AKA delayed DLL loading с точки зрения Loader'а ничем не отличается от использования LoadLibrary()/GetProcAddress(). При этом компилятор генерирует дополнительный код, который загружает DLL по первому обращению.

Я веду к тому, что программист должен сам сказать OS как загружать библиотеки, иначе все они будут загружаться согласно выбору по-умолчанию, т.е. при старте.

Raider комментирует...

То есть в случае отложенной загрузки генерируется некий оверхед, и поэтому по умолчанию отложенная загрузка отключениа для уменьшения оверхеда.

В принципе, логично. Логично что отключена.

Not a kernel guy комментирует...

> То есть в случае отложенной
> загрузки генерируется некий
> оверхед

Почти так. Оверхед есть, но скорее всего он одноразовый и им можно пренебречь. Основная причина - совместимость. Любое изменение логики NT Loader влечет за собой жуткий хвост проблем с совместимостью. А так получается, что delayed loading это просто надстройка над стандартным механизмом. Кроме того, только программист действительно может определить когда правильно загружать DLL. Компилятор и линкер тоже пасуют.