Реалізація відкладеної завантаження бібліотек на С + + h2>
Андрій Солодовников p>
Ви все ще вантажте бібліотеки вручну? p>
Тоді ми йдемо до вас! p>
Коротка передісторія
h2>
За специфікою моєї роботи мені досить часто доводиться
вручну завантажувати бібліотеки та динамічно, за допомогою GetProcAddress,
імпортувати безліч функцій. Це відбувається частково тому, що потрібно
забезпечити сумісність з різними версіями Windows, у яких цільові функції
можуть бути відсутніми, почасти тому, що так буває зручніше (наприклад, при
реалізації механізму плагінів). Звичайно, завжди хочеться це автоматизувати,
особливо якщо функцій і бібліотек багато. З одного боку, в лінійці Visual C + +
для цього є підтримка компіляторалінкера у вигляді механізму Delay Load, з
іншого боку, існує думка, що використовувати цей метод є поганим
тоном, і, напевно, це так. Одна з основних причин, яку хочеться зазначити
особливо - цей механізм є microsoft-specific, тобто ніяких гарантій,
що написаний Вами код буде працювати і на інших компіляторах або платформах,
немає. Більше того, кілька разів «попав» на дивну поведінку цього механізму
(наприклад, див Q218613),
ми від його використання у своїх проектах відмовилися. p>
Наступним кроком був пошук готового підходящого
функціонала. Як не дивно, такого не було, не дивлячись на те, що проблема
дійсно має місце бути. Багато рішень були дуже прості і
неоптимальні (наприклад, це
рішення). Вони не дозволяли визначати імпорт відразу декількох функцій з однієї
бібліотеки, або для цього потрібно було написати пристойну кількість коду. Вони
викликали GetProcAddress і LoadLibrary в будь-який час, коли їм заманеться, а на
насправді - мало не при кожному зверненні до імпортованої функції. Інші
(наприклад, таке
рішення) було досить складно і незручно використовувати. p>
ПРИМІТКА p>
Насправді, вказані варіанти
цілком можуть бути використані в невеликих проектах, коли не потрібно
імпортувати велику кількість функцій. Однак їх використання в будь-якому
випадку вимагає досить багато посидючості і терпіння, принаймні, мене
це не влаштовувало. p>
І загальний недолік всіх цих рішень - вони були і є
неоптимальні. Особливо це стосується кількості коду, що генерується компілятором
(та й програмістом) на одну імпортовану функцію і швидкодії отриманого
коду. p>
Все це, укупі з витраченим часом, змусило мене
до необхідності написання чергового велосипеда в вигляді бібліотеки емуляції
Delay Load, а також і цієї статті. P>
Вимоги до бібліотеки, що реалізує механізм Delay
load h2>
У цьому параграфі ми розглянемо більш детально, яким
базовим вимогам повинен задовольняти механізм динамічного завантаження
бібліотек. p>
Виходячи з описаного вище, можна сформулювати
наступні вимоги до механізму підтримки динамічного завантаження бібліотек: p>
Як можна більша незалежність від компілятора С + + (в
межах ANSI C + +). Мінімальні вимоги до компілятору - бібліотека повинна
бути повністю функціональна на всіх Visual C + + компіляторах, починаючи з Visual
C + + 6.0; p>
Мінімальна кількість коду, що генерується компілятором,
що припадає на одну імпортовану функцію; p>
Зручність визначення в проекті імпортованих
бібліотекфункцій; h2>
Можливість завдання своїх стратегій (реакцій) на
помилки завантаження бібліотекінахожденія функції; p>
Мінімізація викликів LoadLibrary. Для однієї бібліотеки
(модуля) виклик LoadLibrary повинен проводиться один раз незалежно від
кількості імпортованих з неї функцій. Даний механізм повинен працювати не
тільки в межах однієї одиниці трансляції, але і проекту в цілому. Таким
чином, повинна створюватися єдина для програми таблиця використовуваних модулів;
p>
Мінімізація викликів GetProcAddress. GetProcAddress
повинен викликатися тільки при першому зверненні до імпортованої функції, в
Надалі всі виклики імпортованої функції повинні проводитися безпосередньо; p>
Бібліотека повинна забезпечувати звичний синтаксис
дзвінка - не повинно бути ніяких зовнішніх відмінностей від звичайного виклику функції з
С/С + +; p>
повинні підтримуватися UNICODE версії імпортованих
функцій, причому, бажано, на основі заголовків від відповідних статично
лінкуемих бібліотек, в яких визначено відповідні макроси. p>
З описаних вище вимог найбільш важливими і
цікавими представляються пункти 3,5,6 і 7. Особливості їх реалізації будуть
розглянуті більш докладно далі разом з програмною реалізацією бібліотеки.
Для тих, кому деталі реалізації не цікаві, а цікаві методи використання
бібліотеки, пропонується приступати відразу до розділу Використання бібліотеки. p>
Пропонована реалізація бібліотеки
p>
Клас, інкапсулює роботу з модулями
p>
Для початку розглянемо вимогу (5) до реалізації
завантаження бібліотекмодулей. Очевидно, що забезпечити виконання даного
вимоги з умовою унікальності примірника бібліотеки в межах всіх одиниць
трансляції проекту можна використанням патерну Singlton для завантаження
модуля. При цьому для кожного різного завантаження модуля повинен створюватися
власний примірник Синглтон, який і буде забезпечувати «одноразову»
завантаження в конструкторі і надалі вивантаження бібліотеки в деструктор. Ця
завдання вирішується визначенням шаблонного класу CModule. Назва бібліотеки повинно
служити значенням, щодо якого виробляється інстанцірованіе об'єкта,
інкапсулює завантаження бібліотеки. Оскільки як патерну Singlton
використовується Сінглтон Мейерс, то як бонус ми отримуємо відкладену
завантаження бібліотек (оскільки створення екземпляра Сінглтон проводиться при
першому зверненні до породжує функції). p>
РАДА p>
Нагадаю, що найпростіша реалізація Сінглтон
Мейерс виглядає наступним чином: p>
template p>
struct CMeyersSinglton p>
( p>
static T & GetInstance () p>
( p>
static T obj; p>
return obj; p>
) p>
); p>
У зв'язку з цим перший варіант визначення шаблону
CModule міг би виглядати так: p>
template p>
class CModule; p>
Тут слід зробити невеличкий відступ. Як було б
чудово, якби будь-який абстрактний мова програмування, що використовується нами,
забезпечував би будь-яку затребувану нами можливість. Але, очевидно, за
міркувань здорового глузду, це неможливо виконати, тому доводиться користуватися
тим, що є. А є така неприємна річ - в С + + прямо інстанціювати
шаблон строкових літерали не вийде. Шаблон може бути інстанціювати тільки
константою з external linkage, а рядковий літерал має internal linkage. На
перший погляд, все досить сумно. Проте, як завжди, рішення лежить на
поверхні. Воно дуже просте і очевидне. Ми будемо інстанціювати шаблон
модуля унікальним класом, інкапсулює рядковий літерал. Сам же клас
буде формуватися за допомогою макросів: p>
# define DECLARE_NAME_ID_IMPL (id, name, ret, text) p>
struct NAME_ID (id) p>
( p>
enum (length = sizeof (name )}; p>
static ret GetStr () (return
text (name );} p>
); p>
# define DECLARE_NAME_ID_A (id, name) DECLARE_NAME_ID_IMPL (id, name,
LPCSTR, DL_EMPTY ()) p>
# define DECLARE_NAME_ID (id, name) DECLARE_NAME_ID_IMPL (id, name,
LPCTSTR, _T) p>
Даний клас є універсальним і буде
використаний в подальшому і для представлення імен імпортованих функцій. Але й
тут є один маленький нюанс - оскільки функція GetProcAddress використовує
тільки ANSI рядка, то ми змушені це передбачити, оголосивши додатковий
макрос DECLARE_NAME_ID_A. p>
Отже, у зв'язку з усім вищевикладеним, визначення
шаблону CModule без урахування стратегій буде виглядати так: p>
template p>
class CModule; p>
Тепер додамо стратегії загрузківигрузкі модуля.
Оскільки стратегія контролює процеси, пов'язані із завантаженням і розвантаженням, у
неї повинно бути як мінімум 2 функції. Одна відповідає за завантаження модуля, друга
за його вивантаження: p>
struct CModulePolicy p>
( p>
static HMODULE Load (LPCTSTR
szFileName); p>
static BOOL Free (HMODULE
hModule); p>
); p>
Тепер у нас є все, що необхідно для повного
написання класу CModule. Реалізація його в запропонованій бібліотеці наведена в
лістингу нижче: p>
struct CModuleLoadLibraryPolicy p>
( p>
static HMODULE Load (LPCTSTR
szFileName) p>
( p>
return
:: LoadLibrary (szFileName); p>
) p>
static BOOL Free (HMODULE
hModule) p>
( p>
return:: FreeLibrary (hModule); p>
) p>
); p>
struct CModuleGetModuleHandlePolicy p>
( p>
static HMODULE Load (LPCTSTR
szFileName) p>
( p>
return
:: GetModuleHandle (szFileName); p>
) p>
static BOOL Free (HMODULE
hModule) p>
( p>
return TRUE; p>
) p>
); p>
template p>
class CModule p>
( p>
public: p>
typedef CModule type; p>
typedef Name name_type; p>
static type & GetModule () p>
( p>
# ifdef DL_MT p>
static volatile LONG lMutex =
FALSE; p>
CLWMutex theMutex (lMutex); p>
CAutoLock
autoLock (theMutex); p>
# endif// DL_MT p>
static type Module; p>
return Module; p>
) p>
HMODULE GetModuleHandle () const p>
( p>
return m_hModule; p>
) p>
BOOL IsLoaded () const p>
( p>
return m_hModule! = NULL; p>
) p>
// Caution - use with care. Not thread-safe p>
BOOL UnloadModule () p>
( p>
HMODULE hModule = m_hModule; p>
m_hModule = NULL; p>
return
LoadPolicy:: Free (hModule); p>
) p>
~ CModule () p>
( p>
if (m_hModule) p>
UnloadModule (); p>
) p>
private: p>
CModule () p>
( p>
m_hModule =
LoadPolicy:: Load (name_type:: GetStr ()); p>
) p>
HMODULE m_hModule; p>
); p>
Клас модуля дозволяє явно вивантажувати бібліотеку
(модуль) за допомогою функції UnloadModule, проте користуватися цією можливістю
треба з великою обережністю. p>
Реалізація динамічного пошуку функцій і глобальної
таблиці імпорту
h2>
Тепер розглянемо деталі реалізації пунктів 6 і 7
(пошук адрес імпортованих функцій та їх виклик). Це найбільш нетривіальна і
цікава в плані програмування частина бібліотеки, оскільки функції можуть
мати різне число параметрів, а також різні типи повертаються значень.
І нагадаю основна вимога - природний синтаксис виклику функцій і
мінімізація звернень до GetProcAddress. p>
У даному випадку для забезпечення вимоги мінімізації
викликів GetProcAddress ми будемо використовувати техніку створення проксі-функцій.
Фактично, під час першого виклику імпортованої функції ми будемо потрапляти в
сформовану компілятором проксі-функцію, в якій буде проводитись пошук
адреси функції з її імені в бібліотеці і в залежності від успішності пошуку
провадиться або виклик функції, або виконання операції, заданої в стратегії
реакції на помилки пошуку. Для того, щоб надалі викликалася
безпосередньо імпортована функція, а не проксі, адреса, отриманий в проксі,
запам'ятовується в глобальній для всіх одиниць трансляції таблиці покажчиків на
функції. Для створення таблиці використовується техніка, подібна застосовуваної в
Сінглтон Мейерс. У дуже спрощеному вигляді це виглядає так: p>
template p>
struct CGlobalProxyTable p>
( p>
static FARPROC & GetProxy () p>
( p>
static FARPROC proxy; p>
return proxy; p>
) p>
); p>
У даному прикладі для кожного вхідного типу буде
згенерований унікальний глобальний вказівник типу FARPROC, фактично
що є осередком глобальної в термінах одиниць трансляцій таблиці функцій. p>
Для того, щоб визначити інтерфейс комірки таблиці
функцій, з'ясуємо, від чого залежить імпортована функція. Очевидно, це ім'я
функції, модуль, з якого треба її імпортувати, і проксі, який використовується для
визначення адреси функції в завантажується бібліотеці. У зв'язку з цим визначимо
клас CDynFunction, інкапсулює комірку для зберігання адреси функції в
глобальної таблиці імпортуються функцій: p>
template
p>
class CDynFunction p>
З огляду на все вищесказане, реалізація класу
тривіальна і буде виглядати так: p>
template p>
class CDynFunction p>
( p>
public: p>
typedef CDynFunction type; p>
typedef Proxy proxy_type; p>
typedef Module module_type; p>
typedef Name name_type; p>
p>
static typename
proxy_type:: fun_type & GetProxy () p>
( p>
static typename proxy_type:: fun_type
proxy = proxy_type:: template Proxy :: ProxyFun; p>
return proxy; p>
) p>
static BOOL InitFunction () p>
( p>
# ifdef DL_MT p>
static volatile LONG lMutex =
FALSE; p>
# endif// DL_MT p>
const module_type
& theModule = module_type:: GetModule (); p>
if (theModule.IsLoaded ()) p>
return DL_GetProcAddressImpl ( p>
# ifdef DL_MT p>
lMutex, p>
(const
FARPROC) proxy_type:: template Proxy :: ProxyFun, p>
# endif// DL_MT p>
(volatile FARPROC
&) GetProxy (), p>
theModule.GetModuleHandle (), p>
name_type:: GetStr () p>
); p>
return FALSE; p>
) p>
); p>
Функція DL_GetProcAddressImpl являє собою
обгортку GetProcAddress, і винесено в окремий функціональний елемент для
зменшення розміру коду за підтримки багатопоточності. Статичний метод
GetProxy () поверне глобальний в сенсі одиниць трансляції адреса в таблиці
функцій, причому спочатку за цією адресою знаходиться адреса проксі функції.
Таким чином, викликаючи функцію за вказівником, отриманим за допомогою GetProxy (),
ми спочатку викликаємо проксі, а в подальшому будемо викликати імпортовану функцію
напряму. p>
Реалізація проксі функцій h2>
До цього моменту все було досить очевидно і
досить просто. Проте при спробі реалізації класу, що визначає функціонал
проксі-функції, ми стикаємося з проблемами. Щоб зрозуміти, в чому вони
полягають, розглянемо параметри, необхідні для створення проксі функції.
Це: p>
тип значення, що повертається імпортованої функції; p>
список типів параметрів імпортованої функції; p>
стратегія реакції на помилку пошуку функції в модулі; p>
тип осередку глобальної таблиці покажчиків на
імпортовані функції (CDynFunction), який буде використаний при створенні
проксі. p>
Як відомо, С + + не підтримує шаблони зі змінним
кількістю параметрів. У зв'язку з цим доведеться використовувати генерацію
примірників шаблону за допомогою макросів а-ля boost:: preprocessor. Пояснювати
детально тут, як це працює, я не буду - це тема для окремої статті.
Крім того, все це задоволення ускладнюється тим, що Visual C 6.0 не може
повертати з void функції тип void. Для вирішення цієї проблеми доводиться
створювати окремі класи для «нормальних» типів і для void, а потім
використовувати спеціалізацію шаблону за значення, що повертається з подальшим
спадкуванням. p>
Розглянемо реалізацію, пропоновану в бібліотеці: p>
# define FUN_PROXY (n) DL_CAT (CFunProxy, n) p>
# define FUN_PROXY_IMPL (n) DL_CAT (FUN_PROXY (n), Impl) p>
# define DECLARE_FUN_PROXY (param_count) p>
template p>
struct FUN_PROXY_IMPL (param_count) p>
( p>
template struct RetProxy p>
( p>
static R WINAPI
ProxyFun (DL_REPEAT_PARAM_N (param_count, P, v)) p>
( p>
if
(DynFunction:: InitFunction ()) p>
return
DynFunction:: GetProxy () (DL_REPEAT_N (param_count, v )); p>
return Policy:: template
FunctionTrait :: MakeReturn (); p>
) p>
); p>
); p>
p>
template <> p>
struct FUN_PROXY_IMPL (param_count) p>
( p>
template struct RetProxy p>
( p>
static void WINAPI
ProxyFun (DL_REPEAT_PARAM_N (param_count, P, v)) p>
( p>
if (DynFunction:: InitFunction ()) p>
DynFunction:: GetProxy () (DL_REPEAT_N (param_count,
v )); p>
else p>
Policy:: template
FunctionTrait :: MakeReturn (); p>
) p>
); p>
); p>
p>
template > p>
struct FUN_PROXY (param_count) p>
( p>
typedef R (WINAPI
* fun_type) (DL_REPEAT_N (param_count, P )); p>
typedef R ret_type; p>
template struct Proxy: public
FUN_PROXY_IMPL (param_count) :: template RetProxy p>
( p>
); p>
); p>
Ключовим у реалізації є макрос
DECLARE_FUN_PROXY (param_count), який визначає шаблон класу проксі-функції
з кількістю параметрів імпортованої функції, зазначеним у param_count. У
результаті застосування цього макросу породжується набір шаблонних класів
проксі-функцій для кількості параметрів від 1 до 16. Макроси DL_REPEAT_N і
DL_REPEAT_PARAM_N формують список формальних і названих параметрів
відповідно. p>
В цілому, після підстановки макросів, що отримується клас
для кількості параметрів n виглядає так: p>
template > p>
struct CFunProxyn p>
( p>
typedef R (WINAPI
* fun_type) (P1, P2, .., Pn )); p>
typedef R ret_type; p>
template
struct Proxy: public CFunProxynImpln :: template
RetProxy p>
( p>
); p>
); p>
Ключовим є вкладений шаблон Proxy, саме він
успадковує проксі-функцію ProxyFun з CFunProxynImpl. Клас CFunProxynImpl
необхідний через неможливість повернути тип void за допомогою оператора return в
Visual C + + 6.0. Як обхідного маневру використовується спеціалізація
реалізації проксі за типом значення, що повертається - окремо для типу void і
окремо для всіх інших типів. p>
Проксі-функція ProxyFun буде использована в
CDynFunction для початкової ініціалізації адреси покажчика на функцію: p>
static typename proxy_type:: fun_type & GetProxy () p>
( p>
static typename
proxy_type:: fun_type proxy = proxy_type:: template
Proxy :: ProxyFun; p>
return proxy; p>
) p>
Для забезпечення можливості реакції на помилку
знаходження функції в модулі використовується відповідна стратегія. Стратегія
складається з класу, вкладеного в нього шаблону, що приймає як параметр
тип комірки таблиці імпортуються функцій і має статичну функцію
MakeReturn, яка й викликається при помилку знайти адресу функції або при
помилку завантаження бібліотеки. На даний момент реалізовані 2 стратегії. Одна
(CFunProxyThrowPolicy) - викидає виключення (за замовчуванням CDynFunException)
при помилку пошуку функціізагрузкі бібліотеки, інша (CFunProxyValuePolicy) --
повертає певний користувачем значення: p>
template p>
struct CFunProxyThrowRetTypeTrait p>
( p>
template p>
struct FunctionTraitImpl p>
( p>
static R MakeReturn () p>
( p>
F:: MakeReturnImpl (); p>
return R (); p>
) p>
); p>
); p>
template <> p>
struct CFunProxyThrowRetTypeTrait p>
( p>
template p>
struct FunctionTraitImpl p>
( p>
static void MakeReturn () p>
( p>
F:: MakeReturnImpl (); p>
) p>
); p>
); p>
template p>
struct CFunProxyThrowPolicy p>
( p>
template p>
struct FunctionTrait: public
CFunProxyThrowRetTypeTrait :: template
FunctionTraitImpl > p>
( p>
static void MakeReturnImpl () p>
( p>
TCHAR
szMessage [DynFunction:: name_type:: length + 64]; p>
_stprintf (szMessage,
_T ( "Can'n resolve procedure <% s>:% d"),
DynFunction:: name_type:: GetStr (), GetLastError ()); p>
throw E (szMessage); p>
) p>
); p>
); p>
// we need not implement void return type value policy, p>
// coz void function can only throw on error p>
template p>
struct CFunProxyValuePolicy p>
( p>
template p>
struct FunctionTrait p>
( p>
static typename
DynFunction:: proxy_type:: ret_type MakeReturn () p>
( p>
return value; p>
) p>
); p>
); p>
Останні штрихи
p>
Власне, на цьому основні елементи бібліотеки
реалізовані, тепер необхідно описати базові макроси, які дозволять
використовувати її більш просто. У бібліотеці для оголошення імпортованих функцій
використовується інтерфейс, що сильно нагадує карту повідомлень MFC. Інтерфейс
складається з 3-х типів макросів. p>
Макроси, що визначають модуль і відкривають секцію
імпортованих з нього функцій (DL_USE_xxx_BEGIN); p>
Макроси, що визначають імпортовані функції
(DL_DECLARE_FUN_xxx); p>
Макрос, що закриває секцію імпорту
(DL_USE_MODULE_END). p>
Таким чином, традиційне оголошення динамічно
імпортованих з бібліотеки функцій виглядає як p>
// оголошення бібліотеки та
простору імен функцій, що імпортуються з неї p>
DL_USE_MODULE_xxx_BEGIN (name_space, "some_lib.dll") p>
DL_DECLARE_FUN_xxx (ImportedFunction1Name, ...
) p>
DL_DECLARE_FUN_xxx (ImportedFunction2Name, ...
) p>
... p>
DL_USE_MODULE_END () p>
Виходячи з описаного інтерфейсу, визначені наступні
базові макроси: p>
Макрос DL_USE_MODULE_LOAD_POLICY_BEGIN (nmspace, name,
load_policy) p>
# define DL_USE_MODULE_LOAD_POLICY_BEGIN (nmspace, name, load_policy) p>
namespace nmspace p>
( p>
DECLARE_NAME_ID (DL_CAT (_MODULE_, nmspace),
name) p>
typedef
delayload:: CModule
module_type; p>
визначає в просторі імен nmspace (тим самим
відкриваючи секцію імпорту функцій для даної бібліотеки) клас модуля,
використовуваного для завантаження бібліотеки з ім'ям name, при цьому застосовуючи політику
завантаження load_policy. Також у просторі імен функцій імпортованої
бібліотеки визначається тип module_type, який являє собою тип класу
модуля для даної бібліотеки і може бути використаний для управління часом
житті бібліотеки, наприклад, для її вивантаження за допомогою статичного методу
UnloadModule. P>
Макрос DL_DECLARE_FUN_ERR_POLICY (name_id, r, p, pl) p>
# define DL_DECLARE_FUN_ERR_POLICY (name_id, r, p, pl) p>
DECLARE_NAME_ID_A (name_id, DL_STRINGIZE (name_id)) p>
static r (WINAPI * & name_id) (DL_SEQ_ENUM (p)) =
delayload:: CDynFunction
>:: GetProxy (); p>
визначає посилання name_id на покажчик на функцію з
ім'ям name_id, типом значення, що повертається r, списком параметрів p і
політикою реакції на помилку завантаження бібліотекіпоіска функції pl. Спочатку
цей покажчик вказує на відповідну проксі-функцію, однак після
першого виклику функції покажчик вказує безпосередньо на саму функцію.
Таким чином, використання імпортованої функції з програми тривіально --
це звичайний виклик функції з простору імен (nmspace:: name_id). p>
неочевидно, але цікавою особливістю такої
реалізації стає те, що автоматично додається підтримка UNICODE
версій імпортованих функцій при підключенні заголовків від відповідних
статично лінкуемих бібліотек, де визначені макроси ІмяФункцііW і
ІмяФункцііA. P>
Використання бібліотеки
p>
Так як під час створення бібліотеки однією з основних
цілей було забезпечення простоти її використання, то найбільш підходящим
інтерфейсом оголошення імпортованих бібліотек і функцій виявився інтерфейс,
зовні нагадує карти повідомлень MFC. У бібліотеці визначено декілька
макросів, які значно спрощують її використання. Це макроси: p>
DL_USE_MODULE_BEGIN (nmspace, name) - відкриває секцію
імпорту функцій з бібліотеки. Параметр nmspace - назва простору імен, у
яке буде вміщено імпортовані функції, name - ім'я бібліотеки, яку
необхідно завантажити. Для завантаження використовується LoadLibrary; p>
DL_USE_MODULE_NON_LOAD_BEGIN (nmspace, name) --
аналогічно попередньому, однак для завантаження використовується GetModuleHandle; p>
DL_DECLARE_FUN (name_id, r, p) - визначає функцію з
ім'ям name_id, типом значення, що повертається r, та списком типів параметрів p в
вигляді (type1) (type2) ... (typen). У разі помилки при завантаженні бібліотекіпоіске
функції з функції повертається значення r (). Для функцій з повертається
значенням void використання даного макросу не має сенсу, оскільки
розпізнати помилку можливим не трапиться (а в разі Visual C + + 6.0 це
просто не скомпіліруется); p>
DL_DECLARE_FUN_ERR (name_id, r, p, e) - аналогічно
попереднього, проте у разі помилки при завантаженні бібліотекіпоіске функції повертається
НЕ r (), а значення, вказане в параметрі e; p>
DL_DECLARE_FUN_THROW (name_id, r, p) - аналогічно
попереднього, проте у разі помилки при завантаженні бібліотекіпоіске функції
викидається виключення CDynFunException; p>
DL_USE_MODULE_END () - закриває секцію імпорту функцій
з модуля. p>
При виконанні функції буде використовуватися синтаксис
nmspace:: name_id. p>
Розглянемо приклад використання бібліотеки в реальному
програмі: p>
# include "stdafx.h" p>
# include p>
# include
"../delayimphlp.h" p>
// оголошення секції
імпорту з kernel32.dll p>
DL_USE_MODULE_BEGIN (kernel, "kernel32.dll") p>
DL_DECLARE_FUN_ERR (GetProcAddress, FARPROC,
(HMODULE) (LPCTSTR), NULL) p>
DL_DECLARE_FUN (GetModuleHandle,
HMODULE, (LPCTSTR)) p>
DL_DECLARE_FUN_THROW (InitializeCriticalSection,
void, (LPCRITICAL_SECTION)) p>
DL_USE_MODULE_END () p>
int main (int argc, char * argv []) p>
( p>
try p>
( p>
CRITICAL_SECTION cs; p>
HMODULE hm =
kernel:: GetModuleHandle ( "ntdll.dll "); p>
kernel:: InitializeCriticalSection (& cs); p>
FARPROC p =
kernel:: GetProcAddress (hm, "NtQuerySystemInformation "); p>
) p>
catch
(delayload:: CDynFunException & E) p>
( p>
:: MessageBox (NULL,
E. GetMessage (), NULL, MB_OK | MB_ICONERROR); p>
) p>
return 0; p>
) p>
У даному прикладі ми завантажуємо бібліотеку kernel32.dll,
потім імпортуємо з неї функції GetProcAddress, GetModuleHandle і
InitializeCriticalSection. Як бачимо, все досить просто і тривіально. У
разі наявності стандартних заголовків до статично лінкуемим бібліотекам, де
за допомогою макросів визначені ANSI і UNICODE варіанти імпортованих функцій,
при підключенні цих заголовків залежно від типу проекту (ANSI або
UNICODE), відповідним чином будуть змінюватися і динамічно імпортовані
функції, забезпечуючи імпорт коректних версій функцій. p>
Висновок
h2>
Отже, в даній статті розглянуто інструментарій,
що дозволяє зручно використовувати в коді безліч динамічно завантажуваних
бібліотек та імпортованих з них функцій, попутно розглянувши кілька
цікавих прийомів програмування на C + + в умовах обмеженої підтримки
шаблонів. Бібліотека вийшла, на мій погляд, досить гнучка і добре
розширюється, вимагає досить мало ресурсів в плані памятікода і одержуваний
при її використанні результат у більшості випадків за швидкодією не поступається
статично імпортованим функцій. Багато чого в ній реалізовано так, а не інакше, з
розрахунку підтримки якомога більшої кількості компіляторів. Бібліотека
перевірялася на працездатність з Visual C + + 6.0, 7.0 і 7.1, але особливих проблем
при портування на інші компілятори (окрім, мабуть, лінійки від Borland)
бути не повинно. Автор висловлює подяку всім учасникам обговорення даної
бібліотеки на форумі RSDN за корисні думки, поради та поправки. Сподіваюся, що
дана бібліотека допоможе хоча б частково спростити життя програмістам WinAPI
і не тільки. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>