Xreferat.com » Рефераты по информатике и программированию » Реализация отложенной загрузки библиотек на С++

Реализация отложенной загрузки библиотек на С++

};

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 и не только.

Если Вам нужна помощь с академической работой (курсовая, контрольная, диплом, реферат и т.д.), обратитесь к нашим специалистам. Более 90000 специалистов готовы Вам помочь.
Бесплатные корректировки и доработки. Бесплатная оценка стоимости работы.

Поможем написать работу на аналогичную тему

Получить выполненную работу или консультацию специалиста по вашему учебному проекту
Нужна помощь в написании работы?
Мы - биржа профессиональных авторов (преподавателей и доцентов вузов). Пишем статьи РИНЦ, ВАК, Scopus. Помогаем в публикации. Правки вносим бесплатно.

Похожие рефераты: