Delphi 3. Библиотека программиста

Присоединение DLL на стадии выполнения


Иногда программа может прекрасно работать без некоторых DLL. Вспомним пример с DLL для преобразования файлов в текстовом редакторе. Пользователи не так уж часто занимаются преобразованием файлов. Скорее всего, большинству из них вообще никогда не придется этим занимать ся. Со стороны программы было бы прямо-таки преступно требовать наличия этих DLL для обычного редактирования файлов. Но именно это и происходит при статическом импорте! Если Windows не сможет найти DLL при загрузке программы, она выдаст сообщение об ошибке и завершит программу.

Кроме того, статический импорт DLL не обладает достаточной гибкостью. Если компилятор должен заранее знать, какие DLL потребуются программе, мы возвращаемся к старой проблеме — чтобы работать с новым форматом, придется исправлять исполняемый файл программы. Нехорошо.

На помощь приходит динамический импорт. Вместо того чтобы заставлять Windows автоматически загружать и подключать DLL при загрузке программы, почему бы не позволить самой программе при необходимости загрузить DLL и подключиться к ней? В этом случае программа будет работать даже без DLL, хотя и не сможет выполнять некоторые функции. Утакого подхода есть еще одно достоинство — программа может сообщить пользователю о причине возникшей проблемы. Если у пользователя где-нибудь сохранилась копия DLL, он сможет скопировать ее в нужное место и попробовать снова— при этом ему даже не придется перезапускать программу.

Глава 2•32-разрядные DLL в Delphi — когда, зачем и как

В листинге 2.3 содержится новая версия интерфейсного модуля BEEPER.DLL. Директивы условной компиляции позволяют выбрать тип импорта — статический или динамический.

Листинг 2.3. Динамический импорт DLL на стадии выполнения

{ BEEPDLL.PAS — интерфейсный модуль для BEEPER.DLL Автор: Джим Мишель Дата последней редакции: 12/05/97 } unit BeepDLL; {$DEFINE DYNAMIC} { закомментируйте эту строку, чтобы реализовать статический импорт } interface {$IFDEF DYNAMIC} { Объявления процедур для динамического импорта } procedure BeepMe; procedure BeepMeTwo; procedure BeepMeThree; {$ELSE} { Объявления процедур для статического импорта } procedure BeepMe; external "beeper.dll"; procedure BeepMeTwo; external "beeper.dll" name "BeepMe"; procedure BeepMeThree; external "beeper.dll" index 1; {$ENDIF} implementation {$IFDEF DYNAMIC} uses Windows;

type BeepMeProc = procedure; var LibInstance : HMODULE; { Логический номер модуля DLL } BeepMePtr : BeepMeProc; procedure BeepMe; begin if (LibInstance = 0) then begin { если DLL еще не загружена, попытаемся загрузить } LibInstance := LoadLibrary("beeper.dll"); { Если LoadLibrary возвращает 0, произошла ошибка } if (LibInstance = 0) then begin MessageBox (0, "Can"'t load BEEPER.DLL', "Error", MB_ICONEXCLAMATION or MB_OK); Exit; end; { DLL загружена, теперь попытаемся найти функцию } BeepMePtr := BeepMeProc (GetProcAddress (LibInstance, "BeepMe")); { Если GetProcAddress возвращает Nil, у нас возникли проблемы} if (Not Assigned (BeepMePtr)) then begin { Предварительно выгрузим DLL, чтобы пользователь заменил ее, если это возможно } FreeLibrary (LibInstance); LibInstance := 0; MessageBox (0, "Can"'t find BeepMe function in DLL.', "Error", MB_ICONEXCLAMATION or MB_OK); Exit; end; end; BeepMePtr; end; procedure BeepMeTwo; begin BeepMe; end; procedure BeepMeThree; begin BeepMe; end; initialization LibInstance := 0; BeepMePtr := Nil; finalization { Если DLL была загружена, ее обязательно нужно выгрузить } if (LibInstance <> 0) then begin FreeLibrary (LibInstance); LibInstance := 0; end; end. {$ELSE} end. {$ENDIF}

Я же предупреждал, что этот вариант сложнее!

Да, динамический импорт действительно получается более сложным. Вам приходится вручную программировать те действия, которые Windows автоматически выполняет при запуске программы в случае статического импорта. С другой стороны, этот код более устойчив к ошибкам. Давайте потратим несколько минут и посмотрим, как он работает.

Прежде всего, имена процедур не связываются с функциями DLL непосредственно в интерфейсной (interface) секции модуля, а соответствуют обычным процедурам, определенным в секции реализации (implementation). Именно ключевое слово external вызывает автоматическую загрузку DLL при запуске программы; если удалить его, Windows не станет загружать DLL.

Затем мы определяем процедурный тип и две переменные:

type
BeepMeProc = procedure;

var
LibInstance : HMODULE; { Логический номер экземпляра DLL }
BeepMePtr : BeepMeProc;

Процедурный тип BeepMeProc похож на типы обработчиков событий Delphi. Переменная этого типа (в данном случае  BeepMePtr) содержит указатель на процедуру, не имеющую параметров. После того как мы загрузим библиотеку BEEPER.DLL и найдем в ней процедуру BeepMe, ее адрес присваивается переменной BeepMePtr.

LibInstance — логический номер (handle) экземпляра BEEPER.DLL, который возвращается функцией LoadLibrary, если загрузка DLL прошла успешно.

Процедуры BeepMeTwo и BeepMeThree являются псевдонимами для BeepMe, поэтому в версии с динамическим импортом они просто вызывают процедуру BeepMe модуля.

Все волшебство происходит внутри BeepMe. Прежде всего процедура проверяет, загружена ли DLL. Если DLL еще не загружена, процедура вызывает функцию API LoadLibrary, которая ищет DLL и пытается загрузить ее, а также выполняет код запуска DLL (об этом подробно рассказано ниже), после чего возвращает логический номер модуля, который однозначно определяет DLL. Если DLL не найдена или при загрузке произошла ошибка, LoadLibrary возвращает 0, а BeepMe выдает сообщение об ошибке.

Если функция LoadLibrary успешно загрузила DLL, мы вызываем функцию GetProcAddress, которая пытается найти в загруженной DLL функцию с именем BeepMe. Адрес найденной функции присваивается переменной BeepMePtr. Если GetProcAddress не может найти заданную функцию, она возвращает Nil, в результате чего BeepMe выдает сообщение об ошибке и выгружает DLL из памяти.

Если все прошло нормально, то есть DLL была успешно загружена, а процедура BeepMe — найдена, она вызывается через указатель BeepMePtr.

Последнее замечание — ваша программа должна явно выгрузить (используя процедуру FreeLibrary) все DLL, загруженные с помощью LoadLibrary. Для этого используются секции initialization и finalization. При запуске модуля секция initialization присваивает переменным LibInstance и BeepMePtr стандартные значения, означающие, что DLL не загружена. При выходе из программы секция finalization выгружает DLL, если она была загружена ранее.



Содержание раздела