Pure C++ delay load
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.08.04 13:16
Оценка: 105 (10)
Приветствую всех.
Немного предыстории. По специфике моей работы мне довольно часто приходится вручную загружать библиотеки, резолвить множество функции. Отчасти потому, что требуется обеспечить совместимость с различными версиями виндовс, отчасти потому, что так бывает удобнее. Конечно, всегда хочется это автоматизировать. С одной стороны, есть поддержка компилятора\линкера в виде Delay Load, с другой стороны, бытует мнение, что использовать этот метод является дурным тоном (и наверное, это так. Несколько раз попав на странное поведение этого механизма, мы от него отказались). Существует достаточно много уже написанных велосипедов, но меня они по ряду причин не устроили. Объяснять все долго, вкратце — некоторые сделаны достаточно "тупо" и неоптимально — т.е. вызывают GetProcAddr и LoadLibrary на каждом шагу, другие не позволяют кастомизировать процесс резолвинга и т.д. и т.п (тот же пример на RSDN, на мой згляд, весьма показателен). Ну да ладно, это все хорошо, а что предлагается.
Предлагается следующее:

    1. Гибкая структура определения функций. Синтаксис достаточно прост и расширяем.
    2. Возможность задания своих policy на ошибки загрузки функции.
    3. Минимальный оверхед — создается _единая_ на все модули таблица указателей на функции, при первом вызове используется прокси с поиском функции, в дальнейшем — прямой вызов. Единая таблица загруженных модулей.
    4. Привычный синтаксис вызова — нет никаких FunResolver<Type>()(params), все внешне выглядит обычным вызовом функции из неймспейса (да так оно на самом деле и есть).

Итак, приступим. Сам код достаточно на мой взгляд понятен и адаптирован под VC6 и будет приведен в слудующем постинге. При желании можно портировать и под другие компиляторы.

А сечас сразу приведу пример использования:

// function table definition && declaration

USE_MODULE_BEGIN(kernel, *"*/ 'kern','el32','.dll' /*"*)
    DECLARE_FUN_P1_THROW(GetModuleHandle, *"*/ 'GetM','odul','eHan','dleA' /*"*, HMODULE, LPCSTR)
    DECLARE_FUN_P1(GetModuleHandleW, *"*/ 'GetM','odul','eHan','dleW' /*"*, HMODULE, LPCWSTR)
USE_MODULE_END

......
//  example

HMODULE hm = kernel::GetModuleHandle("ntdll.dll");
HMODULE hm2 = kernel::GetModuleHandleW(L"user32.dll");


Немного пояснений:
Минимальный размер таблицы указателей и их кросс-модульность обеспечивается синглтоном CDynFunction.
Основная проблема, решаемая в приведенном коде — невозможность в С++ инстанцироваться строковым литералом. Приходится извращаться числовыми литералами.
Многое из кода сделано именно так под VC6. Иначе там либо сложно, либо нельзя (ну или я не знаю как).
Публикуется только код для функций с одним параметром. Думаю, при помощи копи\пасте или макросов не составит труда значительно расширить количество параметров.

На самом деле, конечно, у этого решения тоже есть концептуальные недостатки. Например, если в разных модулях для одной функции задать разные policy, то сформируется несколько указателей (сколько policy) + для каждого будет свой прокси и вызываться GetProcAddr. Был и другой вариант кода, где эта проблема была решена отделением CDynFunction от прокси. Фактически, формировалась единая глобальная таблица указателей, а для каждого модуля была своя локальная копия со своими же прокси. Мне почему то показалось, что такой оверхед это слишком и что обычно в одном приложении следуют одинаковой стратегии на обработку ошибок, поэтому текущий подход в общем случае вызовет меньший оверхед в плане использования памяти.

Все это интересно пообсуждать, если будут дельные предложения — поправить\улучшить, поскольку сейчас реализована только концепция. Если получится хорошо — можно все причесать (например, добавить поддержку многопотоковости) и попробовать выложить в статьи как альтернативу CDynaLinkResolver.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.