Динамически подключаемые библиотеки
8.1. Назначение
Динамически подключаемые библиотеки (Dynamic Link Library, DLL) предоставляют универсальный механизм интегрирования в программу процедур и функций, написанных пользователем или другими программистами и, в общем случае, на других, нежели Delphi, языках программирования.
DLL реализуются в виде исполняемых модулей, содержащих готовые к работе процедуры и функции. С точки зрения программиста, есть много общего между DLL и обычными модулями, т.к., в конечном счете, и библиотеки, и модули поставляют подпрограммы, избавляющие программиста от написания собственного кода. Но есть и принципиальные отличия. Главным из них является то, что DLL не в состоянии поставлять в программу переменные, константы и типы, поскольку создатели DLL могут использовать нетипизированные языки программирования, например, язык ассемблера. В результате библиотеки DLL не могут экспортировать в программу столь необходимые сегодня программисту классы - для этого используются пакеты.
Другим важным отличием является способ связывания экспортируемых подпрограмм с основной программой. Модули связываются с программой на этапе компоновки, т.е. статически. Если загружены две программы, использующие одни и те же модули, в памяти будут два экземпляра одинаковых фрагментов кода. В отличие от этого DLL подключаются к программе в момент ее исполнения, т.е. динамически. Если, опять же, две программы используют одну и ту же DLL, в памяти будет лишь один экземпляр разделяемого программами кода. Следует уточнить, что речь идет о физической памяти компьютера. Поскольку каждая программа получает в свое распоряжение виртуальное адресное пространство, в эти пространства будут отображаться столько образов DLL, сколько программ ее используют.
Динамическое подключение DLL дает еще одно немаловажное преимущество над модулями: изменение любой DLL в большинстве случаев не требует перекомпиляции использующей ее программы.
8.2. Структура библиотеки DLL
Для создания DLL в Delphi введено зарезервированное слово Library, которым должен начинаться текст библиотеки. За словом Library следует идентификатор, но в отличие от объявления модуля он не обязан совпадать с именем файла: имя DLL определяется именем DLL-файла, а не идентификатором, следующим за Library.
Структура текста DLL повторяет структуру обычной программы с тем исключением, что раздел исполняемых операторов в DLL играет ту же роль, что и инициирующая часть модуля: операторы этой части исполняются только один раз в момент загрузки библиотеки в память. Каждое очередное обращение с требованием загрузить библиотеку наращивает на единицу ее счетчик ссылок, но не приводит к выполнению операторов исполняемой части.
Рассмотрим шаблон DLL:
library <имя>;
uses
<используемые модули>;
<объявления и описания функций>
exports
<экспортируемые функции>
begin
<инициализационная часть>
end.
В разделе описаний DLL могут объявляться типы (в том числе и классы), константы и переменные, но они остаются скрытыми от вызывающей программы и могут использоваться только внутри DLL. В разделе описаний помимо стандартных для обычной программы объявлений используется специальный раздел объявления экспортируемых подпрограмм. Этот раздел начинается зарезервированным словом Exports, за которым через запятую перечисляются имена экспортируемых подпрограмм, например:
library MyLibrary;
function MyFunc(...): ...;stdCall;
begin
………
end;
procedure MyProc; stdCall;
begin
………
end;
exports
MyFunc, MyProc;
begin
end.
StdCall говорит о стандартном способе вызова, т.е. о совместимости с Windows API, если его не указать то сторонние разработчики программного обеспечения будут испытывать сложности с подключением к библиотеке. Если не планируется использование библиотеки сторонними пользователями, то можно его не указывать.
Раздел Exports помогает компилятору и компоновщику создать специальный заголовок DLL-модуля, в котором перечисляются имена подпрограмм и адреса их точек входа. В DLL может быть несколько списков Exports, но перечисляемые в них подпрограммы должны быть описаны где-то выше по тексту библиотеки. В разделе Exports указываются только имена функций, без параметров.
8.3. Создание библиотеки DLL
Создание DLL можно рассмотреть на примере сложения двух чисел, где собственно процедура самого сложения будет находиться в файле .dll.
Для создания библиотеки необходимо выбрать пункт меню File ® New ® Other ® DLL Wizard и нажать кнопку Ok. В окне редактора кода появится заготовка файла - библиотечного проекта:
library Project2;
{ 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;
{$R *.res}
begin
end.
Далее надо сохранить все (Save All) в своей папке изменив имя Project2 на другое, например mylib (это имя появится в заголовке окна и после слова library).
Комментарии здесь говорят о том, что если процедуры или функции, которые будут экспортироваться во внешние приложения, будут получать или возвращать переменные строкового типа String, то в самом файле-проекте, а также в файле проекта-приложения, которое будет использовать данную библиотеку, требуется первым подключать модуль ShareMem. Текст комментария между разделами library и uses, можно удалить, а после директивы {$R *.res}, записать функцию сложения:
Function summ(a,b:Integer):Integer; StdCall; {имя summ, тип переменных Integer,
выходной параметр тоже Integer}
begin
result:=a+b;
end;
После описания функции надо записать
exports summ; {expotrs позволяет экспортировать функцию summ, чтобы ее можно
было использовать извне, то есть из любых программ};
Полностью текст библиотеки mylib приведен ниже:
library mylib;
uses
SysUtils,
Classes;
{$R *.res}
Function summ(a,b:Integer):Integer; StdCall;
begin
result:=a+b;
end;
exports summ;
begin
end.
Затем надо набранный код скомпилировать в исполняемый файл библиотеки с расширением .dll. Для этого нужно выполнить команду Project ® Compile mylib или нажать клавиши Ctrl + F9. Библиотека создана, в папке проекта появился файл mylib.dll.
8.4. Подключение библиотеки DLL к проекту
Для организации импорта, т.е. доступа к функциям, экспортируемым из DLL, так же как и для их экспорта, Delphi предоставляет стандартные средства:
procedure MyProc; external 'имя_библиотеки.dll';
function MyFunc(...): ...; external 'имя_библиотеки.dll';
Этот способ называется статическим импортом.
Создадим внешнее приложение, которое использовало бы функцию созданной библиотеки mylib.dll. Создадим проект с формой, представленной на рис. 25.
В разделе implementation после директивы {$R *.dfm} надо записать
Function summ(a,b:Integer):Integer; StdCall; External 'mylib.dll';
Здесь External 'mylib.dll' говорит о том, что описание функции берется извне, т.е. из созданного .dll файла. В случае, если файл mylib.dll и запускаемый проект хранятся в разных каталогах, необходимо после слова External указать путь к файлу mylib.dll.
Рис. 25
В обработчике события OnClick кнопки Button1 (Посчитать) можно записать следующее:
Procedure TForm1.Button1Click(Sender: TObject);
Var c:Integer;
Begin
ShowMessage('Библиотека mylib.dll загружена, можно использовать' +#10#13+ 'нужную библиотечную функцию');
c:=summ(StrToInt(Edit1.Text),StrToInt(Edit2.Text));
Edit3.Text:=IntToStr(c);
End;