Неочевидной, но интересной особенностью такой реализации становится то, что автоматически добавляется поддержка 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.dllDL_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 и не только.