From: Владислав Лазаренко <vlazarenko at miratech.biz>
Newsgroups: email
Date: Mon, 16 Nov 2004 14:31:37 +0000 (UTC)
Subject: Отложенная загрузка библиотек под Windows
Отложенная загрузка библиотек
Владислав Лазаренко, 17 ноябрь 2004
Целостность программного продукта
---------------------------------
Как вы знаете, многие программы зависят от каких-либо динамически
подключаемых библиотек, которые содержат определенный код,
используемый в программе (исполняемом файле). Таким образом, чтобы
сохранить целостность программного продукта, на компьютере
пользователя обязательно должны присутствовать не только исполняемые
файлы, но и необходимые библиотеки. Как правило, такие зависимости
проверяются на этапе установки, и никаких ошибок в последствии не
возникает, но бывают и другие, более сложные ситуации. Например,
заказчик поставил задачу написать программу, которая должна проверять
наличие установленного СУБД клиента (в виде динамически подключаемых
библиотек) и, если клиент установлен, выполнить определенные операции
с СУБД, используя эту библиотеку. А что же произойдет, если какой-то
из библиотек не окажется? Все очень просто - произойдет ошибка на
этапе загрузки исполняемого файла операционной системой. В ОС Windows
, например, будет показано неприветливое окно с надписью " Unable to
locate DLL ". Самое обидно в этой ситуации то, что управление не будет
передано программе, и вы никак не сможете эту ситуацию корректно
обработать, хотя иногда это очень необходимо.
Что такое отложенная загрузка библиотек и для чего она нужна
------------------------------------------------------------
Существует несколько решений описанной выше проблемы. Одно из них -
запускать одну программу из скрипта или другой программы и проверять
корректность её запуска и/или выполнения. Это выглядит не очень
элегантно, к тому же в ОС Windows будет показан диалог, на который
пользователь должен будет среагировать нажатием кнопки " OK ", а это
не всегда приемлемо. Есть второе, более трудоемкое решение; оно
заключается в том, чтобы не линковать программу с динамически
подключаемыми библиотеками, а загружать их на этапе выполнения. Этого
можно добиться с помощью функций загрузки библиотеки и поиска символов
внутри неё. Для ОС Windows это функции LoadLibrary и GetProcAddress ,
в UNIX системах это функции dlopen и dlsym . Все очень просто, в
начале выполнения программы пытаемся загрузить все необходимые функции
из библиотек вручную, если это не удается, завершаем выполнение
программы с определенным кодом ошибки. Это и называется отложенная
загрузка библиотек.
Microsoft идет навстречу программистам
--------------------------------------
Разработчики Visual C ++ решили облегчить жизнь программистам, теперь,
от версии 6.0, не нужно заниматься сложной и кропотливой работой,
писать код для загрузки библиотек и обработки ошибок. Линкер
предоставляет опцию / DelayLoad для отложенной загрузки библиотеки.
Загрузка библиотеки будет автоматически происходить только перед
первым вызовом функции из оной. Хочу заметить, что отложенная загрузка
kernel 32. dll не поддерживается.
Загрузка библиотеки происходит в " user space " с помощью функции,
которая находится в библиотеке delayimp и называется helper функция.
Если вы используете механизм отложенной загрузки, то должны линковать
программу с этой библиотекой.
Указание библиотек для отложенной загрузки
------------------------------------------
Вы можете указать, какие библиотеки должны загружаться по мере
выполнения программы с помощью опции линкера / DelayLoad : DLLName .
Ниже приведен пример отложенной загрузки user 32. dll :
// cl t . cpp user 32. lib delayimp . lib / link / DELAYLOAD : user 32. dll
# include < windows . h >
// не комментируйте следующие две строки, чтобы убрать . libs из командной строки
// #pragma comment (lib, "delayimp")
// #pragma comment (lib, "user32")
int main ()
{
// user 32. dll будет загружена здесь
MessageBox(0, "Hello", "Hello", MB_OK);
return (0);
}
Явная выгрузка библиотеки
-------------------------
Опция линкера / delay : unload позволяет вам явно выгрузить
библиотеку. По умолчанию это делается автоматически тогда, когда
вызовы из этой библиотеки более не используются, при этом адреса этих
вызовов остаются в IAT ( import address table ). Однако если вы
сделаете это вручную с помощью опции / delay : unload и функции
__FUnloadDelayLoadedDLL2, то вспомогательная функция загрузки символов
( helper function ) сбрасывает значения в IAT .
int main()
{
BOOL TestReturn;
// MyDLL.DLL будет загружена тут
fnMyDll ();
// MyDLL . dll будет выгружена тут
TestReturn = __FUnloadDelayLoadedDLL2("MyDll.dll");
if (TestReturn)
( void ) printf (" n библиотека была выгружена");
else
( void ) printf (" n библиотека не была выгружена");
return (0);
}
Имплементацию функции __ FUnloadDelayLoadedDLL 2 вы можете найти в
файле Visual Studio - VC 7 INCLUDE DELAYHLP . CPP .
Загрузка всех символов из библиотеки
------------------------------------
Функция __ HrLoadAllImportsForDll , которая определена в файле
delayhlp . cpp загружает все символы из указанной библиотеки.
Использование этой функции позволяет обработать возможные ошибки в
едином месте вашего кода, без отлавливания исключений на этапе вызова
каждой функции загруженной библиотеки. Пример:
if ( FAILED (__ HrLoadAllImportsForDll (" delay 1. dll ")))
{
( void ) printf ("ошибка загрузки символов n ");
exit(2);
}
Обработка ошибок
----------------
Как и везде, в загрузке тоже может произойти ошибка. Как её
обработать, если вспомогательная функция загрузки вызывается анонимно?
Очень просто, для этого существуют так называемые указатели на функции
обработчики ( hooks ). Есть два таких указателя, это указатель на
функцию обработки ошибок и на функцию, которая обрабатывает
нотификации (оповещения о каком-либо действии). Указатель такого типа
определяется так:
typedef struct DelayLoadInfo {
DWORD cb; // размер структуры
PCImgDelayDescr pidd ; // что угодно здесь
FARPROC * ppfn ; // указатель на загружаемую функцию
LPCSTR szDll ; // имя библиотеки
DelayLoadProc dlp ; // имя процедуры
HMODULE hmodCur ; // hInstance загруженной библиотеки
FARPROC pfnCur ; // функция-обработчик, которая будет вызвана
DWORD dwLastError ; // ошибка (если нотификация об ошибке)
} DelayLoadInfo, * PDelayLoadInfo;
Оповещения
----------
Функция оповещения вызывается перед тем, как должно выполниться то или
иное действие в функции загрузки. Параметром передается флаг
предстоящей операции.
Указатель определяется так (уже определен):
ExternC PfnDliHook __ pfnDliNotifyHook 2;
Флаги операций:
1) Проверка, загружена ли уже библиотека
2) Вызов функции LoadLibrary для обработки библиотеки
3) Вызов функции GetProcAddress для получения адреса процедуры
4) Завершение всех операций
Ошибки
------
Указатель определяется так (уже определен):
ExternC PfnDliHook __pfnDliFailureHook2;
1) Ошибка загрузки библиотеки в память
2) Ошибка поиска адреса процедуры в загруженной библиотеке
Обратите внимание, что в конце имен определенных указателей стоят
цифры 2, это только для Visual Studio версии 7.0, для версии же 6.0
двойки в имени отсутствуют.
* Пример программы, в которой загружается библиотека mydll . dll и
обрабатывается ошибка: