На этом шаге мы рассмотрим динамическую загрузку DLL.
Динамическая загрузка DLL выполняется только тогда, когда она требуется. Кроме того, даже если библиотека или вызываемая функция из этой библиотеки найдена не будет, эту ситуацию можно проанализировать и все равно запустить приложение. Конечно, при этом следует информировать пользователя о невозможности вызвать функцию из DLL, например, сделав недоступным пункт меню, с помощью которого пользователь обращается к данной функции. Пример динамической загрузки DLL выглядит следующим образом:
type TAddFunction = function(K:integer):integer; stdcall; procedure TForm1.Button1Click(Sender: TObject); var Add1:TAddFunction; HLib:THandle; N:integer; begin HLib := 0; try HLib:=LoadLibrary('FirstLib.dll'); if HLib <> 0 then begin Add1 := GetProcAddress(HLib,'CalculateSum'); if Assigned(Add1) then begin N := StrToInt(Edit1.Text); N := Add1(N); Edit1.Text := IntToStr(N); end else ShowMessage('Метод'+ ' CalculateSum не найден'); end else ShowMessage('Не загружена библиотека' + ' FirstLib.dll'); finally if HLib <> 0 then FreeLibrary(HLib); end; end;
Первоначально определяется новый процедурный тип - например, TAddFunction, который имеет тот же самый список параметров и то же самое соглашение о вызовах, что и функция в DLL. Delphi в процессе компиляции приложения никак не может проверить соответствие процедурного типа функции, вызываемой из DLL. Проверка может быть осуществлена только во время выполнения - при несоответствии формальных параметров или неверно указанного соглашения о вызовах происходит крах стека, и программист это очень быстро обнаружит.
Далее в коде приложения вызывается функция LoadLibrary, которая в качестве параметра использует имя библиотеки. После успешной отработки этой функции и загрузки библиотеки в память дескриптор загруженной библиотеки помещается в переменную HLib. Если же не удается найти (или загрузить) библиотеку, то в эту же переменную помещается код ошибки. Для определения факта загрузки библиотеки переменную HLib следует сравнить с нулем. Если библиотека была успешно загружена, то делается попытка найти адрес функции в памяти компьютера путем вызова функции Windows API GetProcAddress, возвращающей адрес функции, имя которой указано во втором параметре GetProcAddress. Если же функция не найдена, возвращается значение nil.
Соответственно, вызов функции Add1 осуществляется, только если она была успешно найдена. Далее, поскольку при загрузке библиотеки были заняты системные ресурсы, их необходимо вновь вернуть системе, выгрузив библиотеку из памяти. Для этого вызывается функция FreeLibrary. При загрузке DLL производится подсчет ссылок - а именно, при каждом успешном обращении к функции LoadLibrary в DLL счетчик ссылок увеличивается на единицу. При каждом вызове функции FreeLibrary счетчик ссылок уменьшается на единицу. Как только счетчик ссылок станет равным нулю, библиотека может быть выгружена из памяти компьютера. Поэтому каждому успешному вызову LoadLibrary должно соответствовать обращение к FreeLibrary - иначе DLL не выгрузится из памяти компьютера до окончания работы приложения.
Поэтому перечисленные функции помещены в защищенный блок try...finally...end - это гарантирует вызов функции FreeLibrary, если происходит исключение.
Функция GetProcAddress может также использовать индексы для загрузки DLL. Для примера, рассмотренного на предыдущем шаге, в котором функция AddOne экспонируется с индексом 1, применение индексов в функции GetProcAddress выглядит следующим образом:
Addl := GetProcAddress(HLib, PChar(1));
Однако при наличии индексов следует соблюдать осторожность. Необходимо, чтобы все функции в DLL были проиндексированы со значениями индексов от единицы до N (N - число функций, объявленных в секции exports). Если какой-либо индекс опускается (в этом случае максимальное значение индекса будет больше числа функций), то GetProcAddress возвращает ненулевой адрес для несуществующего индекса. Очевидно, что этот адрес является недействительным и при попытке обращения к нему генерируется исключение. По-видимому, по причине некорректной работы функции GetProcAddress компания Microsoft запретила использовать индексы для импорта функций из DLL.
Теперь следует рассмотреть, каким образом загружаемая библиотека размещается в памяти компьютера. При загрузке DLL осуществляется резервирование памяти, необходимое для хранения кода функций. Кроме того, резервируется место для всех глобальных переменных и выполняются секции инициализации в модулях DLL. Если другой процесс также пытается загрузить DLL, то вновь происходит резервирование памяти для хранения глобальных переменных. Однако копирование функций DLL не осуществляется. Другими словами, одна копия функции в памяти обслуживает несколько приложений. Глобальные переменные являются уникальными для каждого приложения, и если одно приложение изменит их значение путем вызова какой-нибудь функции, то другое приложение этого не заметит.
Библиотеки COM DLL загружаются только динамически, при обращении клиента к интерфейсу, который находится в данной библиотеке. Для того чтобы библиотека СОМ загружалась сразу же после старта приложения и выгружалась после его окончания (аналог статической загрузки), требуется написание кода, в частности кода вызова функции LockServer для фабрики классов или использования функции CoLoadLibrary.
На следующем шаге мы рассмотрим обмен данными с DLL.