Смекни!
smekni.com

Реализация отложенной загрузки библиотек на С (стр. 2 из 3)

template <class Proxy>struct CGlobalProxyTable{ static FARPROC &GetProxy() { static FARPROC proxy;return proxy; }};

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

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

template <class Module, class Name, class Proxy>class CDynFunction

Учитывая все вышесказанное, реализация класса тривиальна и будет выглядеть так:

template <class Module, class Name, class Proxy>class CDynFunction{public: typedef CDynFunction<Module, Name, Proxy> type; typedef Proxy proxy_type; typedef Module module_type; typedef Name name_type; static typename proxy_type::fun_type &GetProxy() { static typename proxy_type::fun_type proxy = proxy_type::template Proxy<type>::ProxyFun; return proxy; } static BOOL InitFunction() {#ifdef DL_MT static volatile LONG lMutex = FALSE;#endif // DL_MT const module_type &theModule = module_type::GetModule(); if (theModule.IsLoaded()) return DL_GetProcAddressImpl(#ifdef DL_MT lMutex, (const FARPROC)proxy_type::template Proxy<type>::ProxyFun,#endif //DL_MT (volatile FARPROC &)GetProxy(), theModule.GetModuleHandle(), name_type::GetStr()); return FALSE; }};

Функция DL_GetProcAddressImpl представляет собой обертку GetProcAddress, и вынесена в отдельный функциональный элемент для уменьшения размера кода при поддержке многопоточности. Статический метод GetProxy() вернет глобальный в смысле единиц трансляции адрес в таблице функций, причем изначально по этому адресу находится адрес прокси функции. Таким образом, вызывая функцию по указателю, полученному при помощи GetProxy(), мы первоначально вызываем прокси, а в дальнейшем будем вызывать импортируемую функцию напрямую.

Реализация прокси функций

До этого момента все было достаточно очевидно и довольно просто. Однако при попытке реализации класса, определяющего функционал прокси-функции, мы сталкиваемся с проблемами. Чтобы понять, в чем они заключаются, рассмотрим параметры, необходимые для генерации прокси функции. Это:

тип возвращаемого значения импортируемой функции;

список типов параметров импортируемой функции;

стратегия реакции на ошибку поиска функции в модуле;

тип ячейки глобальной таблицы указателей на импортируемые функции (CDynFunction), который будет использован при создании прокси.

Как известно, С++ не поддерживает шаблоны с переменным количеством параметров. В связи с этим придется использовать генерацию экземпляров шаблона при помощи макросов а-ля boost::preprocessor. Объяснять подробно здесь, как это работает, я не буду – это тема для отдельной статьи. Кроме того, все это удовольствие осложняется тем, что Visual C 6.0 не может возвращать из void функции тип void. Для обхода этой проблемы приходится создавать отдельные классы для «нормальных» типов и для void, а затем использовать специализацию шаблона по возвращаемому значению с последующим наследованием.

Рассмотрим реализацию, предлагаемую в библиотеке:

#define FUN_PROXY(n) DL_CAT(CFunProxy,n)#define FUN_PROXY_IMPL(n) DL_CAT(FUN_PROXY(n),Impl)#define DECLARE_FUN_PROXY(param_count) &bsol;template <typename R>&bsol;struct FUN_PROXY_IMPL(param_count)&bsol;{&bsol; template <class DynFunction, DL_REPEAT_N(param_count, typename P), class Policy> struct RetProxy&bsol; {&bsol; static R WINAPI ProxyFun(DL_REPEAT_PARAM_N(param_count, P, v))&bsol; {&bsol; if (DynFunction::InitFunction())&bsol; return DynFunction::GetProxy()(DL_REPEAT_N(param_count, v));&bsol; return Policy::template FunctionTrait<DynFunction>::MakeReturn();&bsol; }&bsol; };&bsol;};&bsol;&bsol;template <>&bsol;struct FUN_PROXY_IMPL(param_count) <void>&bsol;{&bsol; template <class DynFunction, DL_REPEAT_N(param_count, typename P), class Policy> struct RetProxy&bsol; {&bsol; static void WINAPI ProxyFun(DL_REPEAT_PARAM_N(param_count, P, v))&bsol; {&bsol; if (DynFunction::InitFunction())&bsol; DynFunction::GetProxy()(DL_REPEAT_N(param_count, v));&bsol; else&bsol; Policy::template FunctionTrait<DynFunction>::MakeReturn();&bsol; }&bsol; };&bsol;};&bsol;&bsol;template <typename R, DL_REPEAT_N(param_count, typename P), class Policy = CFunProxyValuePolicy<R> >&bsol;struct FUN_PROXY(param_count)&bsol;{&bsol; typedef R (WINAPI *fun_type)(DL_REPEAT_N(param_count, P));&bsol; typedef R ret_type;&bsol; template <class DynFunction> struct Proxy:public FUN_PROXY_IMPL(param_count)<R>::template RetProxy<DynFunction, DL_REPEAT_N(param_count, P), Policy>&bsol;{&bsol; };&bsol;};

Ключевым в реализации является макрос DECLARE_FUN_PROXY(param_count), который определяет шаблон класса прокси-функции с количеством параметров импортируемой функции, указанным в param_count. В результате применения этого макроса порождается набор шаблонных классов прокси-функций для количества параметров от 1 до 16. Макросы DL_REPEAT_N и DL_REPEAT_PARAM_N формируют список формальных и поименованных параметров соответственно.

В целом, после подстановки макросов, получаемый класс для количества параметров n выглядит так:

template <typename R, typename P1, typename P2, …, typename Pn , class Policy = CFunProxyValuePolicy<R> >struct CFunProxyn{&bsol; typedef R (WINAPI *fun_type)(P1, P2, .. , Pn)); typedef R ret_type; template <class DynFunction> struct Proxy:public CFunProxynImpln<R>::template RetProxy<DynFunction, P1, P2, .. ,Pn, Policy>{ };};

Ключевым является вложенный шаблон Proxy, именно он наследует прокси-функцию ProxyFun из CFunProxynImpl. Класс CFunProxynImpl необходим из-за невозможности вернуть тип void при помощи оператора return в Visual C++ 6.0. В качестве обходного маневра используется специализация реализации прокси по типу возвращаемого значения – отдельно для типа void и отдельно для всех остальных типов.

Прокси-функция ProxyFun будет использована в CDynFunction для первоначальной инициализации адреса указателя на функцию:

static typename proxy_type::fun_type &GetProxy(){ static typename proxy_type::fun_type proxy = proxy_type::template Proxy<type>::ProxyFun;return proxy;}

Для обеспечения возможности реакции на ошибку нахождения функции в модуле используется соответствующая стратегия. Стратегия состоит из класса, вложенного в него шаблона, принимающего в качестве параметра тип ячейки таблицы импортируемых функций и имеющего статическую функцию MakeReturn, которая и вызывается при ошибке поиска адреса функции или при ошибке загрузки библиотеки. На данный момент реализованы 2 стратегии. Одна (CFunProxyThrowPolicy) – выбрасывает исключение (по умолчанию CDynFunException) при ошибке поиска функции&bsol;загрузки библиотеки, другая (CFunProxyValuePolicy) – возвращает определенное пользователем значение:

template <class R> struct CFunProxyThrowRetTypeTrait{ template <class F> struct FunctionTraitImpl { static R MakeReturn() { F::MakeReturnImpl(); return R(); } };};template <> struct CFunProxyThrowRetTypeTrait<void>{ template <class F> struct FunctionTraitImpl { static void MakeReturn() { F::MakeReturnImpl(); } };};template<class E = CDynFunException>struct CFunProxyThrowPolicy{ template <class DynFunction> struct FunctionTrait:public CFunProxyThrowRetTypeTrait<typename DynFunction::proxy_type::ret_type>::template FunctionTraitImpl<FunctionTrait<DynFunction> > { static void MakeReturnImpl() { TCHAR szMessage[DynFunction::name_type::length + 64]; _stprintf(szMessage, _T("Can'n resolve procedure <%s>: %d"), DynFunction::name_type::GetStr(), GetLastError()); throw E(szMessage); } };};// we need not implement void return type value policy, // coz void function can only throw on errortemplate<class R, R value = R()>struct CFunProxyValuePolicy{ template <class DynFunction> struct FunctionTrait { static typename DynFunction::proxy_type::ret_type MakeReturn(){ return value; } };};

Последние штрихи

Собственно, на этом основные элементы библиотеки реализованы, теперь необходимо описать базовые макросы, которые позволят использовать ее более просто. В библиотеке для объявления импортируемых функций используется интерфейс, сильно напоминающий карту сообщений MFC. Интерфейс состоит из 3-х типов макросов.

Макросы, определяющие модуль и открывающие секцию импортируемых из него функций (DL_USE_xxx_BEGIN);

Макросы, определяющие импортируемые функции (DL_DECLARE_FUN_xxx);

Макрос, закрывающий секцию импорта (DL_USE_MODULE_END).

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

// объявление библиотеки и пространства имен функций, импортируемых из нееDL_USE_MODULE_xxx_BEGIN(name_space, “some_lib.dll”) DL_DECLARE_FUN_xxx(ImportedFunction1Name, … ) DL_DECLARE_FUN_xxx(ImportedFunction2Name, … )…DL_USE_MODULE_END()

Исходя из описанного интерфейса, определены следующие базовые макросы:

Макрос DL_USE_MODULE_LOAD_POLICY_BEGIN(nmspace, name, load_policy)

#define DL_USE_MODULE_LOAD_POLICY_BEGIN(nmspace, name, load_policy) &bsol;namespace nmspace &bsol;{&bsol; DECLARE_NAME_ID(DL_CAT(_MODULE_, nmspace), name)&bsol; typedef delayload::CModule<NAME_ID(DL_CAT(_MODULE_, nmspace)), load_policy> module_type;

определяет в пространстве имен nmspace (тем самым открывая секцию импорта функций для данной библиотеки) класс модуля, используемого для загрузки библиотеки с именем name, при этом применяя политику загрузки load_policy. Также в пространстве имен функций импортируемой библиотеки определяется тип module_type, который представляет собой тип класса модуля для данной библиотеки и может быть использован для управления временем жизни библиотеки, например, для ее выгрузки при помощи статического метода UnloadModule.

Макрос DL_DECLARE_FUN_ERR_POLICY(name_id, r, p, pl)

#define DL_DECLARE_FUN_ERR_POLICY(name_id, r, p, pl) &bsol;DECLARE_NAME_ID_A(name_id, DL_STRINGIZE(name_id))&bsol;static r (WINAPI *&name_id)(DL_SEQ_ENUM(p)) = delayload::CDynFunction<module_type, NAME_ID(name_id), delayload::FUN_PROXY(DL_SEQ_SIZE(p))<r, DL_SEQ_ENUM(p), pl > >::GetProxy();

определяет ссылку name_id на указатель на функцию с именем name_id, типом возвращаемого значения r, списком параметров p и политикой реакции на ошибку загрузки библиотеки&bsol;поиска функции pl. Изначально этот указатель указывает на соответствующую прокси-функцию, однако после первого вызова функции указатель указывает непосредственно на саму функцию. Таким образом, использование импортируемой функции из программы тривиально – это обычный вызов функции из пространства имен (nmspace::name_id).