
- •Динамически загружаемые библиотеки
- •5.1. Динамически загружаемые библиотеки
- •5.2. Разработка библиотеки
- •5.2.1. Структура библиотеки
- •5.2.2. Экспорт подпрограмм
- •5.2.3. Соглашения о вызове подпрограмм
- •5.3. Использование библиотеки в программе
- •5.3.1. Статический импорт
- •5.3.2. Модуль импорта
- •5.3.3. Динамический импорт
- •5.3.4. Глобальные переменные и константы
- •5.3.5. Инициализация и завершение работы библиотеки
- •5.3.6. Исключительные ситуации и ошибки выполнения подпрограмм
- •5.3.7. Общий менеджер памяти
- •5.3.8. Стандартные системные переменные
- •Программирование на языке Delphi
- •6.1. Понятие интерфейса
- •6.2. Описание интерфейса
- •6.3. Расширение интерфейса
- •6.4. Глобально-уникальный идентификатор интерфейса
- •6.5. Реализация интерфейса
- •6.6. Использование интерфейса
- •6.7. Реализация нескольких интерфейсов
- •6.8. Реализация интерфейса несколькими классами
- •6.9. Связывание методов интерфейса с методами класса
- •6.10. Реализация интерфейса вложенным объектом
- •6.11. Совместимость интерфейсов
- •6.12. Совместимость класса и интерфейса
- •6.13. Получение интерфейса через другой интерфейс
- •6.14. Механизм подсчета ссылок
- •6.15. Представление интерфейса в памяти
- •6.16. Применение интерфейса для доступа к объекту dll-библиотеки
- •6.17. Итоги
- •7.1. Проект
- •7.1.1. Понятие проекта
- •7.1.2. Файлы описания форм
- •7.1.3. Файлы программных модулей
- •7.1.4. Главный файл проекта
- •7.1.5. Другие файлы проекта
- •7.2. Управление проектом
- •7.2.1. Создание, сохранение и открытие проекта
- •7.2.2. Окно управления проектом
- •7.2.3. Группы проектов
- •7.2.4. Настройка параметров проекта
- •7.2.5. Компиляция и сборка проекта
- •7.2.6. Запуск готового приложения
- •7.3. Форма
- •7.3.1. Понятие формы
- •7.3.2. Имя и заголовок формы
- •7.3.3. Стиль формы
- •7.3.4. Размеры и местоположение формы на экране
- •7.3.5. Цвет рабочей области формы
- •7.3.6. Рамка формы
- •7.3.7. Значок формы
- •7.3.8. Невидимая форма
- •7.3.9. Прозрачная форма
- •7.3.10. Полупрозрачная форма
- •7.3.11. Недоступная форма
- •7.3.12. События формы
- •7.4. Несколько форм в приложении
- •7.4.1. Добавление новой формы в проект
- •7.4.2. Добавление новой формы из Хранилища Объектов
- •7.4.3. Переключение между формами во время проектирования
- •7.4.4. Выбор главной формы приложения
- •7.4.5. Вызов формы из программы
- •7.5. Компоненты
- •7.5.1. Понятие компонента
- •7.5.2. Визуальные и невизуальные компоненты
- •7.5.3. «Оконные» и «графические» компоненты
- •7.5.4. Общие свойства визуальных компонентов
- •7.5.5. Общие события визуальных компонентов
- •7.6. Управление компонентами при проектировании
- •7.6.1. Помещение компонентов на форму и их удаление
- •7.6.2. Выделение компонентов на форме
- •7.6.3. Перемещение и изменение размеров компонента
- •7.6.4. Выравнивание компонентов на форме
- •7.6.5. Использование Буфера обмена
- •7.7. Закулисные объекты приложения
- •7.7.1. Application — главный объект, управляющий приложением
- •7.7.2. Screen — объект, управляющий экраном
- •7.7.3. Mouse — объект, представляющий мышь
- •7.7.4. Printer — объект, управляющий принтером
- •7.7.5. Clipboard — объект, управляющий Буфером обмена
- •7.8. Итоги
5.3.3. Динамический импорт
Действия по загрузке и подключению библиотеки (выполняемые при статическом импорте автоматически) можно проделать самостоятельно, обратившись к стандартным функциям операционной системы. Таким образом, импорт можно произвести динамически во время работы программы (а не во время ее запуска).
Для динамического импорта необходимо:
загрузить библиотеку в оперативную память вызовом функции LoadLibrary;
затем извлечь из нее адреса подпрограмм с помощью функции GetProcAddress.
полученные адреса нужно сохранить в процедурных переменных соответствующего типа. После этого вызов подпрограмм библиотеки может выполняться путем обращения к процедурным переменным.
для завершения работы с библиотекой необходимо вызвать функцию 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 для работы с драйверами баз данных.