Действия по загрузке и подключению библиотеки (выполняемые при статическом импорте автоматически) можно проделать самостоятельно, обратившись к стандартным функциям операционной системы. Таким образом, импорт можно произвести динамически во время работы программы (а не во время ее запуска).
Для динамического импорта необходимо:
1.загрузить библиотеку в оперативную память вызовом функции LoadLibrary;
2. затем извлечь из нее адреса подпрограмм с помощью функции GetProcAddress.
3. полученные адреса нужно сохранить в процедурных переменных соответствующего типа. После этого вызов подпрограмм библиотеки может выполняться путем обращения к процедурным переменным.
4. для завершения работы с библиотекой необходимо вызвать функцию FreeLibrary.
Ниже приведено краткое описание функцийLoadLibrary, FreeLibrary и GetProcAddress.
- LoadLibrary(LibFileName: PChar): HModule — загружает в оперативную память библиотеку, которая хранится на диске в файле с именем LibFileName. При успешном выполнении функция возвращает числовой описатель библиотеки, который должен использоваться в дальнейшем для управления библиотекой. Если при загрузке библиотеки призошла какая-нибудь ошибка, то возвращается нулевое значение. Если аргумент LibFileName содержит имя файла без маршрута, то этот файл ищется в следущих каталогах: в каталоге, из которого была запущена главная программа, в текущем каталоге, в системном каталоге операционной системы Windows (его точный маршрут можно узнать вызовом функции GetSystemDirectory), в каталоге, по которому установлена операционная система (его точный маршрут можно узнать вызовом функции GetWindowsDirectory), а также в каталогах, перечисленных в переменной окружения PATH.
- FreeLibrary(LibModule: HModule): Bool — выгружает библиотеку, заданную описателем LibModule, из оперативной памяти и освобождает занимаемые библиотекой ресурсы системы.
- GetProcAddress(Module: HModule; ProcName: PChar): Pointer — возвращает адрес подпрограммы с именем ProcName в библиотеке с описателем Module. Если подпрограмма с именем ProcName в библиотеке не существует, то функция возвращает значение nil (пустой указатель).
В приложении № показано использование техники динамического импорта:
program TestDynamicImport; {$APPTYPE CONSOLE} uses Windows; type TBubleSortProc = procedure (var Arr: array of Integer); stdcall; TQuickSortProc = procedure (var Arr: array of Integer); stdcall; var BubleSort: TBubleSortProc; // указатель на функцию BubleSort QuickSort: TQuickSortProc; // указатель на функцию QuickSort LibHandle: HModule; // описатель библиотеки Arr: array [0..9] of Integer; I: Integer; begin LibHandle := LoadLibrary('SortLib.dll'); if LibHandle <> 0 then begin @BubleSort := GetProcAddress(LibHandle, 'BubleSortIntegers'); @QuickSort := GetProcAddress(LibHandle, 'QuickSortIntegers'); if (@BubleSort <> nil) and (@QuickSort <> nil) then begin Randomize; for I := Low(Arr) to High(Arr) do Arr[I] := Random(100); BubleSort(Arr); for I := Low(Arr) to High(Arr) do Write(Arr[I], ' '); Writeln; for I := Low(Arr) to High(Arr) do Arr[I] := Random(100); QuickSort(Arr); for I := Low(Arr) to High(Arr) do Write(Arr[I], ' '); Writeln; end else Writeln('Ошибка отсутствия процедуры в библиотеке.'); FreeLibrary(LibHandle); end else Writeln('Ошибка загрузки библиотеки.'); Writeln('Press Enter to exit...'); Readln;end. |
В программе определены два процедурных типа данных, которые по списку параметров и правилу вызова (stdcall) соответствуют подпрограммам сортировки BubleSort и QuickSort в библиотеке:
type TBubleSortProc = procedure (var Arr: array of Integer); stdcall; TQuickSortProc = procedure (var Arr: array of Integer); stdcall; |
Эти типы данных нужны для объявления процедурных переменных, в которых сохраняются адреса подпрограмм:
var BubleSort: TBubleSortProc; QuickSort: TQuickSortProc; |
В секции var объявлена также переменная для хранения целочисленного описателя библиотеки, возвращаемого функцией LoadLibrary:
var ... LibHandle: HModule; |
Программа начинает свою работу с того, что вызывает функцию LoadLibrary, в которую передает имя файла DLL-библиотеки. Функция возвращает описатель библиотеки, который сохраняется в переменной LibHandle.
LibHandle := LoadLibrary('SortLib.dll'); if LibHandle <> 0 then begin ... end |
Если значение описателя отлично от нуля, значит, библиотека была найдена на диске и успешно загружена в оперативную память. Убедившись в этом, программа обращается к функции GetProcAddress за адресами подпрограмм. Полученные адреса сохраняются в соответствующих процедурных переменных:
@BubleSort := GetProcAddress(LibHandle, 'BubleSortIntegers'); @QuickSort := GetProcAddress(LibHandle, 'QuickSortIntegers'); |
Обратите внимание на использование символа @ перед именем каждой переменной. Он говорит о том, что выполняется не вызов подпрограммы, а работа с ее адресом.
Если этот адрес отличен от значения nil, значит, подпрограмма с указанным именем была найдена в библиотеке и ее можно вызвать путем обращения к процедурной переменной:
if (@BubleSort <> nil) and (@QuickSort <> nil) then begin ... BubleSort(Arr); ... QuickSort(Arr); ... end |
По окончании сортировки программа выгружает библиотеку вызовом функции FreeLibrary.
Как вы убедились, динамический импорт в сравнении со статическим требует значительно больше усилий на программирование, но он имеет ряд преимуществ:
- Более эффективное использование ресурсов оперативной памяти по той причине, что библиотеку можно загружать и выгружать по мере надобности;
- Более гибкая обработка ошибок. Динамический импорт помогает в тех случаях, когда некоторые процедуры и функции могут отсутствовать в библиотеке. При статическом импорте такие ситуации обрабатывает операционная система, которая выдает сообщение об ошибке и прекращает работу программы. Однако при динамическом импорте программа сама решает, что ей делать, поэтому она может отключить часть своих возможностей и работать дальше.
Динамический импорт отлично подходит для работы с библиотеками драйверов устройств. Он, например, используется самой средой Delphi для работы с драйверами баз данных.