![](/user_photo/2706_HbeT2.jpg)
Razrabotka_DLL
.pdf1
РАЗРАБОТКА ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМЫХ БИБЛИОТЕК
При написании программ часто используется множество повторяющихся процедур, функций, объектов, которые можно объединить в один модуль и подключать его к новым приложениям, экономя время. Эта идея реализуется с помощью так называемых динамически подключаемых библиотек.
ЧТО ТАКОЕ ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМАЯ БИБЛИОТЕКА (DLL)
Динамически подключаемая библиотека представляет собой выполняемый модуль Windows (обычно имеющий расширение DLL), код и ресурсы которого могут использоваться другими динамическими библиотеками и приложениями.
DLL-библиотека похожа на модуль языка Object Pascal – она тоже является библиотекой процедур и функций. Однако если обычный статический модуль подключается к программе на этапе компоновки и составляет неотъемлемую часть выполняемого ЕХЕ-файла, то динамическая библиотека представляет собой самостоятельный модуль и подключается к программе во время ее выполнения. Windows загружает DLL-библиотеки отдельно от кода программ, поэтому несколько приложений могут совместно использовать одну и ту же копию библиотеки в памяти. Кроме того, динамические библиотеки загружаются в память лишь при необходимости и могут выгружаться из нее, освобождая ресурсы для других библиотек и приложений.
Вы можете свободно использовать в Delphi-приложениях процедуры и функции DLL-библиотек, разработанных с помощью других языков и систем программирования. Справедливо и обратное – процедуры и функции DLLбиблиотек, разработанных в Delphi, могут использоваться другими системами программирования. Оба этих обстоятельства делают DLL-библиотеки идеальным средством при разработке приложений с применением нескольких языков программирования.
Чтобы подчеркнуть значение, придаваемое DLL-библиотекам в Windows, отметим одну интересную деталь — сама операционная система Windows почти полностью состоит из динамических библиотек. Модули USER, GDI и др., файлы шрифтов (расширение FOT) и драйверов (расширение DRV) – все это динамически подключаемые библиотеки. Использование DLL-библиотек является нормой практически для всех коммерческих приложений.
РАЗРАБОТКА ПОЛЬЗОВАТЕЛЬСКОЙ ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМОЙ БИБЛИОТЕКИ
СТРУКТУРА DLL-БИБЛИОТЕКИ
По структуре исходный текст библиотеки похож на исходный текст программы, за исключением следующих важных особенностей.
Текст любой DLL-библиотеки должен начинаться с заголовка, состоящего из ключевого слова library, за которым следует имя библиотеки (аналогично заголовку программы, состоящему из ключевого слова program и имени программы), например:
library MyLib;
Все процедуры и функции динамической библиотеки, которые предполагается вызывать из других приложений, должны быть объявлены с ключевым словом stdcall, например:
procedure MyProc (A: Integer; В: Double); stdcall; function MyFunc(X, Y: Integer): Integer; stdcall;
Директива stdcall объясняет компилятору, что для подпрограммы нужно использовать стандартное соглашение вызова, чтобы к ней можно было обратиться из приложения, написанного на другом языке программирования.
Процедуры и функции, экспортируемые DLL-библиотекой, должны быть перечислены в разделе exports. Разделов exports может быть несколько, и они могут располагаться в программе произвольным образом. Пункты раздела exports разделяются запятой, а весь раздел заканчивается точкой с запятой, например:
exports
MyProc, MyFunc;
Если по какой-то причине вы не поместили в текст динамической библиотеки раздел exports, то ни одна из ее процедур и функций не будет экспортирована.
СПОСОБЫ ЭКСПОРТИРОВАНИЯ ПРОЦЕДУР И ФУНКЦИЙ
Каждая экспортируемая процедура или функция, хранимая в DLL-библиотеке, идентифицируется двумя уникальными ключами: числовым индексом и строковым именем. Благодаря индексам и именам выполняется настройка адресов вызывающих программ на процедуры и функции DLL-библиотек. Индекс процедуры или функции представляет собой положительное целое число в диапазоне от 1 до 32767. В качестве имени может выступать любая последо-
вательность символов; имена процедур и функций в DLL-библиотеках «чувствительны» к прописным и строч-
ным буквам.
Раздел exports позволяет указать, какие процедуры и функции динамической библиотеки, с какими индексами и именами должны быть экспортированы. Варианты экспортирования процедур и функций:
exports
MyProcl,
MyProc2 name 'MyDynaProc2',
МуРrосЗ index 3,
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF2x1.jpg)
2
MyFunc4 index 4 name 'MyDynaFunc4',
MyFunc5 index 5 name ' MyDynaFunc5' resident;
Если индекс или имя экспортирования для процедуры или функции не указаны, то компилятор назначает их автоматически: в качестве индекса принимается порядковый номер процедуры или функции, а в качестве имени – имя самой процедуры или функции. Например, процедуре MyProc1 по умолчанию назначается имя MyProc1 (строчные
ипрописные буквы имеют значение). Если индекс при экспортировании процедуры или функции не указан, то вы не вправе делать какие бы то ни было предположения относительно индекса, назначенного компилятором по умолчанию,
ибудете вынуждены импортировать такую процедуру или функцию только по имени. Поэтому нужно всегда задавать индексы явно.
Вразделе exports к имени каждой экспортируемой процедуры или функции может быть добавлено ключе-
вое слово resident. Структура DLL-файла предусматривает наличие специальных таблиц для хранения имен экспортируемых процедур и функций. Если для процедуры или функции задана директива resident, то компилятор помещает ее имя в таблицу резидентных имен DLL-файла, в противном случае – в таблицу нерезидентных имен. Таб-
лица резидентных имен в отличие от таблицы нерезидентных имен всегда загружается в оперативную память вместе с программным кодом DLL-библиотеки и остается там вплоть до ее выгрузки, поэтому директива resident позволяет ускорить поиск точек входа в процедуры и функции по именам (но только для тех процедур и функций, для которых задана эта директива). Однако при этом менее эффективно используется оперативная память.
В теле динамической библиотеки могут быть объявлены типы, константы, переменные, процедуры, функции, объекты и другие элементы, но экспортировать она может только процедуры и функции. Доступ к данным DLLбиблиотеки следует осуществлять с использованием процедурного интерфейса.
ПРИМЕР ДИНАМИЧЕСКИ ПОДКЛЮЧАЕМОЙ БИБЛИОТЕКИ
Приступая к разработке собственной динамически подключаемой библиотеки, запустите Delphi и выберите в меню команду Файл – Новый – Другое
В диалоге, который Delphi откроет на экране, выберите значок с подписью DLL и щелкните на кнопке ОК.
Delphi создаст новый проект с заготовкой для исходного текста DLL-библиотеки:
library Project1;
{Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select
3
Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }
uses
SysUtils,
Classes;
{$R *.res}
begin end.
Добавьте в исходный текст библиотеки определение двух простейших функций: Min для вычисления минимального из двух целых чисел и Мах для вычисления максимального из двух целых чисел. С помощью оператора exports выполните экспортирование этих функций. Наконец, сохраните проект в рабочем каталоге под именем MathLib.dpr. Исходный текст библиотеки приведен ниже.
library MathLib;
{Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }
uses
SysUtils,
Classes;
function Min(x,y:integer):Integer; stdcall; begin
if x<y then Min:=x else Min:=y; end;
function Max(x,y:integer):Integer; stdcall; begin
if x>y then Max:=x else Max:=y; end;
exports
Min, Max;
{$R *.res}
begin end.
После компиляции проекта вы получите в своем рабочем каталоге двоичный файл DLL-библиотеки Math-
Lib.dll.
Динамически подключаемая библиотека MathLib удовлетворяет всем минимальным требованиям: она имеет заголовок, начинающийся с ключевого слова library; функции Min и Мах объявлены с директивой stdcall и определены в разделе exports. В примере MathLib используется простейший вариант синтаксиса раздела exports, при котором функции экспортируются по своим же именам.
Определение динамически подключаемой библиотеки заканчивается главным операторным блоком begin
... end, внутри которого вы можете выполнить любые действия, связанные с инициализацией библиотеки. Дан-
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF4x1.jpg)
4
ный операторный блок будет выполнен при загрузке DLL-библиотеки в оперативную память. Динамически подключаемая библиотека MathLib не требует инициализации, поэтому ее главный операторный блок пустой.
Если DLL-библиотека требует выполнения каких-либо действий перед выгрузкой из памяти (код завершения), то это можно сделать, вставив свою процедуру в цепочку процедур выхода (для этого нужно переопределить значение глобального указателя ExitProc).
ИСПОЛЬЗОВАНИЕ DLL-БИБЛИОТЕКИ В ПРИЛОЖЕНИИ
Для того чтобы процедуры и функции динамически подключаемой библиотеки стали доступны в прикладной программе, они должны быть подключены или, говоря иначе, импортированы. Импорт процедур и функций динамической библиотеки может осуществляться двумя различными способами:
–с помощью директивы компилятора external (статический импорт);
–вручную с использованием функций LoadLibrary и GetProcAddress (динамический импорт). Статический импорт является более удобным и используется намного чаще.
СТАТИЧЕСКИЙ ИМПОРТ
В случае статического импорта в исходный текст программы необходимо поместить external-объявления процедур и функций динамически подключаемой библиотеки, которые предполагается вызывать. После слова external записывается имя библиотеки, представляющее собой строковую константу или строковое выражение. Например, для того чтобы использовать в программе функции Min и Мах динамической библиотеки MathLib, достаточно поместить в тело программы следующие объявления:
function Min (X, Y: Integer): Integer; stdcall; external 'MathLib'; function Max (X, Y: Integer): Integer; stdcall; external 'MathLib';
После этого функции Min и Max можно использовать так, как будто они являются частью самой программы. Создадим проект, использующий эти функции.
unit My_Unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
function Min(x,y:Integer):Integer; stdcall; external 'MathLib'; function Max(x,y:Integer):Integer; stdcall; external 'MathLib';
type
TMy_Form = class(TForm) Button1: TButton; GroupBox1: TGroupBox; Edit1: TEdit;
Edit2: TEdit; GroupBox2: TGroupBox; Label1: TLabel; Label2: TLabel; Edit3: TEdit;
Edit4: TEdit;
procedure Button1Click(Sender: TObject); private
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF5x1.jpg)
5
{Private declarations } public
{Public declarations } end;
var
My_Form: TMy_Form;
implementation
{$R *.dfm}
procedure TMy_Form.Button1Click(Sender: TObject); var a,b:integer;
begin a:=StrToInt(Edit1.Text); b:=StrToInt(Edit2.Text);
edit3.Text:=IntToStr(Min(a,b));
edit4.Text:=IntToStr(Max(a,b));
end;
end.
Данный пример демонстрирует простейший способ импорта. На самом деле существует три способа:
–по имени в программе;
–по имени в DLL-библиотеке;
–по индексу.
Следующий пример демонстрирует импорт некоторых абстрактных процедур по имени в программе, по имени в DLL и по индексу:
procedure MyProcl; stdcall; external 'MyDLL';
procedure MyPrос2; stdcall; external 'MyDLL' name 'MyDynaProc2'; procedure МуРгосЗ; stdcall; external 'MyDLL' index 3;
Если функция импортируется по имени, то в качестве ключа поиска этой функции в DLL-библиотеке принимается в точности то имя, которое она имеет в программе, с учетом строчных и прописных букв. Именно таким образом импортируются функции Min и Мах в программе UseMath.
Лучше всего использовать статический импорт по именам всюду, где это возможно. Он намного удобнее импорта по индексам, хотя работает медленнее. Импорт по индексам оправдан, когда требуется ускорить загрузку DLLбиблиотеки в память.
Обратите внимание, что на момент компиляции программы DLL-библиотека, которую она подключает, может еще и не существовать. Следовательно, разработка приложений и динамических библиотек может осуществляться совершенно независимо; кроме того, перекомпиляция DLL-библиотеки или ее замена на новую версию не требует перекомпиляции ни одного приложения, которые ее используют.
Пример DDL с использованием различных способов импорта: library MathLib;
{ Important note about DLL … }
uses
SysUtils, Classes;
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF6x1.jpg)
6
function Min(x,y:integer):Integer; stdcall; begin
if x<y then Min:=x else Min:=y; end;
function Max(x,y:integer):Integer; stdcall; begin
if x>y then Max:=x else Max:=y; end;
function MinXY(x,y:integer):Integer; stdcall; begin
if x<y then MinXY:=x else MinXY:=y; end;
function MaxXY(x,y:integer):Integer; stdcall; begin
if x>y then MaxXY:=x else MaxXY:=y; end;
function MinAB(x,y:integer):Integer; stdcall; begin
if x<y then MinAB:=x else MinAB:=y; end;
function MaxAB(x,y:integer):Integer; stdcall; begin
if x>y then MaxAB:=x else MaxAB:=y; end;
exports
Min,
Max,
MinXY name 'MyMinXY', MaxXY name 'MyMaxXY', MinAB index 5,
MaxAB index 6;
{$R *.res}
begin end.
Пример программы с использование различных способов импорта:
7
unit My_Unit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
function Min(x,y:Integer):Integer; stdcall; external 'MathLib'; function Max(x,y:Integer):Integer; stdcall; external 'MathLib';
function MinXY(x,y:Integer):Integer; stdcall; external 'MathLib' name 'MyMinXY'; function MaxXY(x,y:Integer):Integer; stdcall; external 'MathLib' name 'MyMaxXY';
function MinAB(x,y:Integer):Integer; stdcall; external 'MathLib' index 5; function MaxAB(x,y:Integer):Integer; stdcall; external 'MathLib' index 6;
type
TMy_Form = class(TForm) Button1: TButton; GroupBox1: TGroupBox; Edit1: TEdit;
Edit2: TEdit; GroupBox2: TGroupBox; Label1: TLabel; Label2: TLabel; Edit3: TEdit;
Edit4: TEdit; GroupBox3: TGroupBox; Label3: TLabel; Label4: TLabel; Edit5: TEdit;
Edit6: TEdit; GroupBox4: TGroupBox; Label5: TLabel; Label6: TLabel; Edit7: TEdit;
Edit8: TEdit; Button2: TButton; Button3: TButton;
procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject);
private
{Private declarations } public
{Public declarations } end;
var
My_Form: TMy_Form;
implementation
{$R *.dfm}
procedure TMy_Form.Button1Click(Sender: TObject); var a,b:integer;
begin a:=StrToInt(Edit1.Text); b:=StrToInt(Edit2.Text);
edit3.Text:=IntToStr(Min(a,b));
edit4.Text:=IntToStr(Max(a,b));
end;
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF8x1.jpg)
8
procedure TMy_Form.Button2Click(Sender: TObject); var a,b:integer;
begin a:=StrToInt(Edit1.Text); b:=StrToInt(Edit2.Text);
edit5.Text:=IntToStr(MinXY(a,b));
edit6.Text:=IntToStr(MaxXY(a,b));
end;
procedure TMy_Form.Button3Click(Sender: TObject); var a,b:integer;
begin a:=StrToInt(Edit1.Text); b:=StrToInt(Edit2.Text);
edit7.Text:=IntToStr(MinAB(a,b));
edit8.Text:=IntToStr(MaxAB(a,b));
end;
end.
ДИНАМИЧЕСКИЙ ИМПОРТ
Действия, которые при статическом импорте выполняет загрузчик динамически подключаемой библиотеки, можно проделывать вручную, обращаясь к соответствующим функциям Windows. Для этого необходимо написать программный код, который загружает DLL-библиотеку в память, а затем получает один за другим адреса точек входа в ее функции. Эти адреса можно сохранить, а затем использовать для вызова функций динамической библиотеки.
Загрузить DDL-библиотеку в память позволяет функция
LoadLibrary(LibFileName: PChar): HModule;
где LibFileName – указатель на нуль-терминированную строку, содержащую имя файла динамической библиотеки. При успешном завершении функция возвращает дескриптор загруженной DLL-библиотеки, а иначе – код ошибки в диапазоне от 0 до 32. Если параметр LibFileName содержит только имя файла без маршрута, то Windows пытается найти библиотеку в соответствии со следующими приоритетами:
1.В каталоге, из которого было запущено приложение.
2.В текущем каталоге.
3.В системном каталоге Windows (его расположение можно узнать, вызвав функцию
GetSystemDirectory).
4.В каталоге Windows (его расположение можно узнать, вызвав функцию GetWindowsDirectory).
5.В каталогах, перечисленных в переменной среды PATH.
Код загрузки динамической библиотеки MathLib может выглядеть так:
var
LibHandle: THandle; begin
LibHandle := LoadLibrary('MathLib.dll');
![](/html/2706/313/html_gAQbbSdFUL.saKu/htmlconvd-BH5MzF9x1.jpg)
9
if LibHandle < 32 then Error;
…
end;
Если в памяти нет загруженной DLL-библиотеки с заданным именем, файл DLL-библиотеки считывается с диска и загружается в оперативную память. Если при вызове LoadLibrary DLL-библиотека уже загружена, Windows создает лишь новое множество глобальных переменных для загружаемой библиотеки, а ее код не дублирует. Счетчик привязок динамической библиотеки увеличивается на 1 при каждом вызове функции LoadLibrary.
После завершения работы с библиотекой ее необходимо освободить вызовом функции
FreeLibrary(LibModule: HModule): Bool;
где LibModule – дескриптор DLL-библиотеки, возвращенный функцией LoadLibrary. Функция уменьшает на 1 счетчик привязок динамической библиотеки и, если он становится равным 0, полностью выгружает ее из оперативной памяти. Функции LoadLibrary и FreeLibrary можно вызывать для одного и того же модуля произвольное число раз, но эти вызовы должны быть сбалансированы.
После загрузки динамической библиотеки вы можете воспользоваться функцией GetProcAddress для получения адресов процедур и функций, которые DLL экспортирует:
GetProcAddress(Module: HModule; ProcName: PChar): Pointer;
где Module – дескриптор загруженного DLL-модуля; ProcName – имя или целочисленный индекс подпрограммы, экспортируемой библиотекой. Если подпрограмма ищется по имени, то в параметре ProcName передается указатель на нуль-терминированную строку, содержащую имя подпрограммы. Если подпрограмма ищется по номеру, то в двух младших байтах параметра ProcName передается ее индекс (положительное целое число в диапазоне от 1 до 32767), а старшие два байта должны быть равны 0. Функция GetProcAddress возвращает указатель на точку входа в экспортируемую подпрограмму.
Подпрограмма с именем ProcName может и не существовать в динамической библиотеке. В этом случае GetProcAddress возвратит nil. Однако если вы пытаетесь получить адрес подпрограммы не по имени, а по индексу, то значение функции GetProcAddress будет не равно nil, даже если подпрограммы с таким индексом нет. Из этого следует вывод: если в программе возможна ситуация, когда подпрограмма DLL-библиотеки отсутствует, поиск следует производить по имени.
С помощью функции GetProcAddress можно получить только адрес процедуры или функции, но не информацию о количестве и типе ее параметров, поэтому при динамическом импорте очень удобными оказываются процедурные типы Object Pascal. Например, процедурный тип функции Min может быть объявлен так:
type
TMin = function (X, Y: Integer) : Integer; stdcall;
Для хранения адреса процедуры или функции DLL-библиотеки в программе объявляется переменная этого
типа: var
Min: TMin;
Если теперь присвоить переменной Min результат вызова GetProcAddress, то ее можно будет использовать в дальнейшем для вызова функции динамической библиотеки.
@Min := GetProcAddress(LibHandle, 'Min');
Обратите внимание на то, что правила согласования типов языка Object Pascal требуют указания символа @ перед переменной Min.
После этого очень просто вызвать функцию Min динамической библиотеки MathLib:
А := Min(В, С);
Пример динамического импорта:
unit My_Unit2;
interface uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
10
type
TMin = function (X, Y: Integer) : Integer; stdcall; TMax = function (X, Y: Integer) : Integer; stdcall;
TMy_Form2 = class(TForm) Button1: TButton; GroupBox1: TGroupBox; Edit1: TEdit;
Edit2: TEdit; GroupBox2: TGroupBox; Label1: TLabel; Label2: TLabel; Edit3: TEdit;
Edit4: TEdit;
procedure Button1Click(Sender: TObject); private
{Private declarations } public
{Public declarations } end;
var
My_Form2: TMy_Form2;
LibHandle: HModule; { дескриптор DLL-библиотеки }
Min: |
TMin; |
{ |
указатель |
на |
функцию |
Min |
} |
Max: |
TMax; |
{ |
указатель |
на |
функцию |
Max |
} |
implementation
{$R *.dfm}
procedure TMy_Form2.Button1Click(Sender: TObject); var a,b:integer;
begin
{ Загрузить DLL }
LibHandle := LoadLibrary('MathLib.dll');
{Проверить, нормально ли загружена DLL } if LibHandle < 32 then
begin
ShowMessage('Could not load DLL.'); Halt;
end;
{Получить адрес функции Min }
@Min := GetProcAddress(LibHandle, 'Min'); { Получить адрес функции Max }
@Max := GetProcAddress(LibHandle, 'Max');
{Проверить корректность указателей } if (@Min = nil) or (@Max = nil) then begin
FreeLibrary(LibHandle);
ShowMessage('Could not link to procedure in DLL.'); Halt;
end;
{Вызвать функции Min и Max } a:=StrToInt(Edit1.Text); b:=StrToInt(Edit2.Text); edit3.Text:=IntToStr(Min(a,b)); edit4.Text:=IntToStr(Max(a,b));
{Освободить библиотеку }
FreeLibrary(LibHandle) ; end;
end.