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




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

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

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

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

тип ячейки глобальной таблицы указателей на импортируемые функции (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) \ template <typename R>\ struct FUN_PROXY_IMPL(param_count)\ {\ template <class DynFunction, DL_REPEAT_N(param_count, typename P), class Policy> struct RetProxy\ {\ static R WINAPI ProxyFun(DL_REPEAT_PARAM_N(param_count, P, v))\ {\ if (DynFunction::InitFunction())\ return DynFunction::GetProxy()(DL_REPEAT_N(param_count, v));\ return Policy::template FunctionTrait<DynFunction>::MakeReturn();\ }\ };\ };\ \ template <>\ struct FUN_PROXY_IMPL(param_count) <void>\ {\ template <class DynFunction, DL_REPEAT_N(param_count, typename P), class Policy> struct RetProxy\ {\ static void WINAPI ProxyFun(DL_REPEAT_PARAM_N(param_count, P, v))\ {\ if (DynFunction::InitFunction())\ DynFunction::GetProxy()(DL_REPEAT_N(param_count, v));\ else\ Policy::template FunctionTrait<DynFunction>::MakeReturn();\ }\ };\ };\ \ template <typename R, DL_REPEAT_N(param_count, typename P), class Policy = CFunProxyValuePolicy<R> >\ struct FUN_PROXY(param_count)\ {\ typedef R (WINAPI *fun_type)(DL_REPEAT_N(param_count, P));\ typedef R ret_type;\ template <class DynFunction> struct Proxy:public FUN_PROXY_IMPL(param_count)<R>::template RetProxy<DynFunction, DL_REPEAT_N(param_count, P), Policy>\ {\ };\ };

Ключевым в реализации является макрос 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 {\ 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) при ошибке поиска функции\загрузки библиотеки, другая (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 error   template<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) \ namespace nmspace \ {\ DECLARE_NAME_ID(DL_CAT(_MODULE_, nmspace), name)\ 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) \ DECLARE_NAME_ID_A(name_id, DL_STRINGIZE(name_id))\ 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 и политикой реакции на ошибку загрузки библиотеки\поиска функции pl. Изначально этот указатель указывает на соответствующую прокси-функцию, однако после первого вызова функции указатель указывает непосредственно на саму функцию. Таким образом, использование импортируемой функции из программы тривиально – это обычный вызов функции из пространства имен (nmspace::name_id).

Неочевидной, но интересной особенностью такой реализации становится то, что автоматически добавляется поддержка UNICODE версий импортируемых функций при подключении заголовков от соответствующих статически линкуемых библиотек, где определены макросы ИмяФункцииW и ИмяФункцииA.

Использование библиотеки

Так как при создании библиотеки одной из основных целей было обеспечение простоты ее использования, то наиболее подходящим интерфейсом объявления импортируемых библиотек и функций оказался интерфейс, внешне напоминающий карты сообщений MFC. В библиотеке определено несколько макросов, которые значительно упрощают ее использование. Это макросы:

DL_USE_MODULE_BEGIN(nmspace, name) – открывает секцию импорта функций из библиотеки. Параметр nmspace – название пространства имен, в которое будет помещены импортируемые функции, name – имя библиотеки, которую необходимо загрузить. Для загрузки используется LoadLibrary;

DL_USE_MODULE_NON_LOAD_BEGIN(nmspace, name) – аналогично предыдущему, однако для загрузки используется GetModuleHandle;

DL_DECLARE_FUN(name_id, r, p) – определяет функцию с именем name_id, типом возвращаемого значения r, и списком типов параметров p в виде (type1)(type2)…(typen). В случае ошибки при загрузке библиотеки\поиске функции из функции возвращается значение r(). Для функций с возвращаемым значением void использование данного макроса не имеет смысла, поскольку распознать ошибку возможным не представится (а в случае Visual C++ 6.0 это просто не скомпилируется);

DL_DECLARE_FUN_ERR(name_id, r, p, e) – аналогично предыдущему, однако в случае ошибки при загрузке библиотеки\поиске функции возвращается не r(), а значение, указанное в параметре e;

DL_DECLARE_FUN_THROW(name_id, r, p) – аналогично предыдущему, однако в случае ошибки при загрузке библиотеки\поиске функции выбрасывается исключение CDynFunException;

DL_USE_MODULE_END() – закрывает секцию импорта функций из модуля.

При вызове функции будет использоваться синтаксис nmspace::name_id.

Рассмотрим пример использования библиотеки в реальной программе:

#include "stdafx.h" #include <windows.h> #include "../delayimphlp.h" // объявление секции импорта из kernel32.dll   DL_USE_MODULE_BEGIN(kernel, "kernel32.dll") DL_DECLARE_FUN_ERR(GetProcAddress, FARPROC, (HMODULE)(LPCTSTR), NULL) DL_DECLARE_FUN(GetModuleHandle, HMODULE, (LPCTSTR)) DL_DECLARE_FUN_THROW(InitializeCriticalSection, void, (LPCRITICAL_SECTION)) DL_USE_MODULE_END()   int main(int argc, char* argv[]) { try { CRITICAL_SECTION cs; HMODULE hm = kernel::GetModuleHandle("ntdll.dll"); kernel::InitializeCriticalSection(&cs); FARPROC p = kernel::GetProcAddress(hm, "NtQuerySystemInformation"); } catch (delayload::CDynFunException &E) { ::MessageBox(NULL, E.GetMessage(), NULL, MB_OK | MB_ICONERROR); } return 0; }

В данном примере мы загружаем библиотеку kernel32.dll, затем импортируем из нее функции GetProcAddress, GetModuleHandle и InitializeCriticalSection. Как видим, все достаточно просто и тривиально. В случае наличия стандартных заголовков к статически линкуемым библиотекам, где при помощи макросов определены ANSI и UNICODE варианты импортируемых функций, при подключении этих заголовков в зависимости от типа проекта (ANSI или UNICODE), соответствующим образом будут меняться и динамически импортируемые функции, обеспечивая импорт корректных версий функций.

Заключение

Итак, в данной статье рассмотрен инструментарий, позволяющий удобно использовать в коде множество динамически загружаемых библиотек и импортируемых из них функций, попутно рассмотрев несколько интересных приемов программирования на C++ в условиях ограниченной поддержки шаблонов. Библиотека получилась, на мой взгляд, достаточно гибкая и хорошо расширяемая, требует достаточно мало ресурсов в плане памяти\кода и получаемый при ее использовании результат в большинстве случаев по быстродействию не уступает статически импортируемым функциям. Многое в ней реализовано так, а не иначе, из расчета поддержки как можно большего количества компиляторов. Библиотека проверялась на работоспособность с Visual C++ 6.0, 7.0 и 7.1, но особых проблем при портировании на другие компиляторы (кроме, пожалуй, линейки от Borland) быть не должно. Автор выражает благодарность всем участникам обсуждения данной библиотеки на форуме RSDN за полезные мысли, советы и поправки. Надеюсь, что данная библиотека поможет хотя бы частично упростить жизнь программистам WinAPI и не только.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-03 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: