Создание активных библиотек в delphi. Создание и использование динамических библиотек (DLL) в Delphi. После текста функции припишем

Не раз приходилось получать письма с просьбой рассказать о создании и использовании DLL в Delphi. В этой статье мы с Вами во всем разберемся и создадим свою библиотеку. Динамически связанные библиотеки (Dinamic Link Library) дают возможность различным приложениям при своей работе использовать общий набор ресурсов. Важно то, что процедуры и функции, помещенные в DLL, выполняются внутри того процесса, который их использует. DLL предоставляет для всех приложений одну единственную копию ресурса, который совместно используется всеми запросившими его приложениями, в отличие от подпрограмм, которые запускают для каждого вызвавшего их приложения свою отдельную копию. Так же в отличия DLL от подпрограмм можно включить и то, что DLL могут экспортировать только процедуры и функции, но не типы, константы и т.д.

Хочется привести отрывок из «Уроков по Дельфи» о структуре DLL в памяти:

DLL - библиотека, в отличие от приложения не имеет ни стека, ни очереди сообщений. Функции, помещенные в DLL, выполняются в контексте вызвавшего приложения, пользуясь его стеком. Но эти же функции используют сегмент данных, принадлежащий библиотеке, а не копии приложения. В силу такой организации DLL, экономия памяти достигается за счет того, что все запущенные приложения используют один модуль DLL, не включая те или иные стандартные функции в состав своих модулей. Часто, в виде DLL создаются отдельные наборы функций, объединенные по тем или иным логическим признакам, аналогично тому, как концептуально происходит планирование модулей (в смысле unit) в Pascal. Отличие заключается в том, что функции из модулей Pascal компонуются статически - на этапе линковки, а функции из DLL компонуются динамически, то есть в run-time.

Создание DLL

Структура DLL мало чем отличается от обычной структуры модуля в Object Pascal. Начинаться DLL должна со слова Library, за которым помещается название библиотеки. Функции и процедуры, которые DLL будет предоставлять другим пользователям (экспортировать), перечисляются после директивы exports.

Для каждой процедуры или функции можно указать ее номер с помощью директивы Index. Если номер будет отсутствовать, то компилятор проведет автоматическую индексацию. Вместо номера процедуры можно использовать уникальное имя, которое задается с помощью директивы name. Если же не указано ни имени функции, ни ее номера, то Дельфи воспримет это как экспорт по имени, которое будет совпадать с названием функции.

Библиотека может содержать также и код инициализации, который будет выполнен при ее загрузке. Он помещается между begin и end. Вот общая структура DLL:

Library First_Dll; uses <используемые модули>; <объявления и описания функций> exports <экспортируемые функции> <описание процедур и функций> begin <инициализационная часть> end.

Приведу примеры описания экспортируемых функций в разделе exports:

Exports Function1 index 2; Fucntion2 name "My_sqr"; Function3;

Как Вы уже догадались, имя функции может не совпадать с именем для экспорта!!!

Использование DLL

Модуль, в котором необходимо использовать процедуры и функции из DLL должен использовать директиву external. Существует два способа использования DLL (динамический и статический). В первом случае, приложение вызывающее функцию из DLL знает имя библиотеки и точку входа в нее, при этом предполагается, что библиотека не меняется. Во втором случае перед использованием DLL следует убедиться, что требуемая библиотека существует, и в ней есть необходимая подпрограмма.

Импортировать подпрограмму можно по ее имени и номеру. Поиск подпрограммы по номеру происходит быстрее, но всегда это удобно.

Ниже приведены примеры импорта функций из нашей First_DLL, которая была рассмотрена в примере:

{ импорт по специфицированному имени } Function ImportByName; external "First_DLL" name "My_sqr"; { импорт по индексу } Function ImportByOrdinal; external "First_DLL" index 2; { импорт по оригинальному имени } Function Fucntion3; external "First_DLL";

Мы рассмотрели статический метод использования DLL. Но в некоторых случаях заранее не известно какая именно потребуется библиотека, поэтому следует воспользоваться динамическим методом:

Uses WinTypes, WinProcs, ... ; type TMyProc = procedure ; var Handle: THandle; MyImportProc: TMyProc; begin Handle:=LoadLibrary("FirstDLL"); if Handle>=32 then { если <=32 - ошибка! } begin @MyImportProc:=GetProcAddress(Handle,"My_sqr"); if MyImportProc<>nil then ...... {здесь используем полученную функцию} end; FreeLibrary(Handle); end;

Но по-моему все что здесь написано не очень-то понятно и хочется реальных завершенных примеров. Я всегда расстраивался, когда в статьях было мало примеров, а одна только теория, поэтому предлагаю Вашему вниманию простой пример использования DLL.

Нажмите в меню File -> New и выберите DLL. Сохраните готовый шаблон, как предлагается, под именем Project1.dpr.

Ниже приведен его полный код:

Library Project1; uses SysUtils,Classes; Function Function1(x,y:integer):integer; export; bgin result:=x+y; end; Function Function2(x,y:real):real; export; var t:real; begin t:=exp(y*ln(x)); result:=t; end; exports Function1 index 1, Function2 name "My_sqr"; begin end.

Здесь две функции, первая вычисляет сумму двух чисел и экспортируется по номеру, а вторая вычисляет x в степени y и экспортируется по имени.

Теперь создадим новый проект и сохраним его под каким-нибудь другим именем, например, DemoDLL. Разместим на форме две кнопки, щелчок на первой из них будет вызывать первую процедуру, а щелчок на второй - вторую. Вот полный код этого демо-проекта:

Unit demo; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function ImportSumm(x,y:integer):integer; external "Project1" index 1; //импорт по номеру function ImportSqr(x,y:real):real; external "Project1" name "My_sqr"; //импорт по имени procedure TForm1.Button1Click(Sender: TObject); var t:real; begin //Узнаем сколько же будет два в третьей степени t:=ImportSqr(2,3); Showmessage(FloatTostr(t)); end; procedure TForm1.Button2Click(Sender: TObject); var t:integer; begin //Узнаем сколько же будет 10+10 t:=ImportSumm(10,10); Showmessage(IntTostr(t)); end; end.

Программист Delphi, MySQL. Образование: высшее. Специальность: программное обеспечение информационных технологий.

Предлагаю вашему вниманию очередной выпуск рассылки, в котором я продолжаю обсуждать
вопросы разработки и использования DLL в Borland Delphi. Для новых подписчиков сообщаю,
что первую часть статьи они могут посмотреть в архиве рассылки, выпуск номер 13.
Прошу прощения у тех, кто писал мне, но не получил ответа. В ближайшее время постараюсь это исправить.
Итак, продолжим.

Прежде чем начать использование какой-либо процедуры или функции, находящейся в динамической библиотеке,
вам необходимо загрузить DLL в оперативную память. Загрузка библиотеки может быть осуществлена
одним из двух способов: статическая загрузка и динамическая загрузка.
Оба метода имеют как преимущества, так и недостатки.
Статическая загрузка означает, что динамическая библиотека загружается автоматически
при запуске на выполнение использующего ее приложения. Для того чтобы использовать такой способ загрузки,
вам необходимо воспользоваться ключевым словом external при описании экспортируемой из
динамической библиотеки функции или процедуры. DLL автоматически загружается при старте программы,
и Вы сможете использовать любые экспортируемые из нее подпрограммы точно так же,
как если бы они были описаны внутри модулей приложения.
Это наиболее легкий способ использования кода, помещенного в DLL .
Недостаток метода заключается в том, что если файл библиотеки, на который
имеется ссылка в приложении, отсутствует, программа откажется загружаться.
Смысл динамического метода заключается в том, что вы загружаете библиотеку не при старте приложения,
а в тот момент, когда вам это действительно необходимо. Сами посудите, ведь если функция, описанная
в динамической библиотеке, используется только при 10% запусков программы, то совершенно нет
смысла использовать статический метод загрузки. Выгрузка библиотеки из памяти в данном случае
также осуществляется под вашим контролем. Еще одно преимущества такого способа
загрузки DLL - это уменьшение (по понятным причинам) времени старта вашего приложения.
А какие же у этого способа имеются недостатки? Основной, как мне кажется, - это то, что использование
данного метода является более хлопотным, чем рассмотренная выше статическая загрузка.
Сначала вам необходимо воспользоваться функцией Windows API LoadLibrary .
Для получения указателя на экспортируемой процедуры или функции должна
использоваться функция GetProcAddress. После завершения использования библиотеки DLL
должна быть выгружена с применением FreeLibrary.
Вызов процедур и функций, загруженных из DLL.
Способ вызова процедур и функций зависит от того, каким образом вы загрузили динамическую библиотеку,
в которой эти подпрограммы находятся.
Вызов функций и процедур из статически загруженных DLL достаточно прост. Первоначально в приложении
должно содержаться описание экспортируемой функции (процедуры). После этого вы можете их использовать
точно так же, как если бы они были описаны в одном из модулей вашего приложения.
Для импорта функции или процедуры, содержащейся в DLL , необходимо использовать
модификатор external в их объявлении. К примеру, для рассмотренной нами выше процедуры HelloWorld
в вызывающем приложении должна быть помещена следующая строка:
procedure SayHello(AForm: TForm); external myfirstdll.dll";
Ключевое слово external сообщает компилятору, что данная процедура может быть найдена в
динамической библиотеке (в нашем случае - myfirstdll.dll).
Далее вызов этой процедуры выглядит следующим образом:
...
HelloWorld(self);
...
При импорте функции и процедур будьте особенно внимательны при написании их имен и интерфейсов!
Дело в том, что в процессе компиляции приложения не производится проверки на правильность имен объектов,
экспортируемых из DLL, осуществляться не будет, и если вы неправильно описали какую-нибудь функцию,
то исключение будет сгенерировано только на этапе выполнения приложения.
Импорт из DLL может проводиться по имени процедуры (функции), порядковому номеру или
с присвоением другого имени.
В первом случае вы просто объявляете имя процедуры и библиотеку, из которой ее импортируете
(мы это рассмотрели чуть выше). Импорт по порядковому номеру требует от вас указание этого самого номера:
procedure HelloWorld(AForm: TForm); external myfirstdll.dll index 15;
В этом случае имя, которое вы даете процедуре при импорте не обязательно должно совпадать с тем,
которое было указано для нее в самой DLL. Т.е. приведенная выше запись означает,
что вы импортируете из динамической библиотеки myfirstdll.dll процедуру, которая в ней экспортировалась
пятнадцатой, и при этом в рамках вашего приложения этой процедуре дается имя SayHello.
Если вы по каким-то причинам не применяете описанный выше способ импорта,
но тем не менее хотите изменить имя импортируемой функции (процедуры), то можно воспользоваться третьим методом:
procedure CoolProcedure; external myfirstdll.dll name "DoSomethingReallyCool";
Здесь импортируемой процедуре CoolProcedure дается имя DoSomethingReallyCool.
Вызов процедур и функций, импортируемых из динамически загружаемых библиотек
несколько более сложен, чем рассмотренный нами выше способ. В данном случае требуется объявить
указатель на функцию или процедуру, которую вы собираетесь использовать.
Помните процедуру HelloWorld? Давайте посмотрим, что необходимо сделать для того,
чтобы вызвать ее на выполнение в случае динамической загрузки DLL. Во-первых, вам
необходимо объявить тип, который описывал бы эту процедуру:
type
THelloWorld = procedure(AForm: TForm);
Теперь вы должны загрузить динамическую библиотеку, с помощью GetProcAddress получить
указатель на процедуру, вызвать эту процедуру на выполнение, и, наконец, выгрузить DLL из памяти.
Ниже приведен код, демонстрирующий, как это можно сделать:

DLLInstance: THandle ;

HelloWorld: THelloWorld;

begin

{ загружаем DLL }

{ получаем указатель }

{ вызываем процедуру на выполнение }

HelloWorld(Self ) ;

{ выгружаем DLL из оперативной памяти }

FreeLibrary(DLLInstance) ;

end ;

Как уже говорилось выше, одним из недостатков статической загрузки DLL является невозможность
продолжения работы приложения при отсутствии одной или нескольких библиотек. В случае с динамической
загрузкой у вас появляется возможность программно обрабатывать такие ситуации и не допускать, чтобы программа
вываливалась» самостоятельно. По возвращаемому функциями LoadLibrary и GetProcAddress значениям можно
определить, успешно ли прошла загрузка библиотеки и найдена ли в ней необходимая приложению процедура.
Приведенный ниже код демонстрирует это.

procedure TForm1.DynamicLoadBtnClick (Sender: TObject ) ;

type

THelloWorld = procedure (AForm: TForm) ;

DLLInstance: THandle ;

HelloWorld: THelloWorld;

begin

DLLInstance:= LoadLibrary("myfirstdll.dll" ) ;

if DLLInstance = 0 then begin

MessageDlg("Невозможно загрузить DLL" , mtError, [ mbOK] , 0 ) ;

Exit ;

end ;

@HelloWorld:= GetProcAddress(DLLInstance, "HelloWorld" ) ;

if @HelloWorld nil then

HelloWorld (Self )

else

MessageDlg("Не найдена искомая процедура!." , mtError, [ mbOK] , 0 ) ;

FreeLibrary(DLLInstance) ;

end ;

В DLL можно хранить не только код, но и формы.
Причем создание и помещение форм в динамическую библиотеку не слишком сильно отличается от работы
с формами в обычном проекте. Сначала мы рассмотрим, каким образом можно написать библиотеку,
содержащую формы, а затем мы поговорим об использовании технологии MDI в DLL.
Разработку DLL, содержащую форму, я продемонстрирую на примере.
Итак, во-первых, создадим новый проект динамической библиотеки.
Для этого выберем пункт меню File|New, а затем дважды щелкнем на иконку DLL .
После этого вы увидите примерно следующий код:

Сохраните полученный проект. Назовем его DllForms.dpr.
Теперь следует создать новую форму. Это можно сделать по-разному.
Например, выбрав пункт меню File|New Form. Добавьте на форму какие-нибудь компоненты.
Назовем форму DllForm и сохраним получившийся модуль под именем DllFormUnit.pas .
Вернемся к главному модулю проекта и поместим в него функцию ShowForm, в задачу которой будет входить
создание формы и ее вывод на экран. Используйте для этого приведенный ниже код.

Form: TDLLForm;

begin

Result:= Form.ShowModal ;

Form.Free ;

end ;

Обращаю внимание, что для того, чтобы проект был скомпилирован без ошибок, необходимо добавить в секцию uses модуль Forms .
Экспортируем нашу функцию с использованием ключевого слова exports:
exports
ShowForm;
Компилируем проект и получаем файл dllforms.dll. Эти простые шаги - все,
что необходимо сделать для сОбратите внимание, что функция ShowForm объявлена с использованием ключевого слова stdcall .
Оно сигнализирует компилятору использовать при экспорте функции соглашение
по стандартному вызову (standard call calling convention). Экспорт функции таким образом создает
возможность использования разработанной DLL не только в приложениях, созданных в Delphi.
Соглашение по вызову (Calling conventions) определяет, каким образом передаются аргументы при вызове функции.
Существует пять основных соглашений: stdcall, cdecl, pascal, register и safecall.
Подробнее об этом можно узнать, посмотрев раздел " Calling Conventions " в файле помощи Delphi.
Также обратите внимание, что значение, возвращаемое функцией ShowForm ,
соответствует значению ShowModal. Таким образом вы можете передавать некоторую информацию
о состоянии формы вызывающему приложению.
Ниже представлено два листинга, первый из которых содержит полный код файла
проекта DLL (модуль с формой здесь не приводится), а второй - модуль вызывающего приложения,
в котором используется только что разработанная нами библиотека.

library DllForms;

uses

DllFormUnit in "DllFormUnit.pas" {DllForm} ;

{$R *.RES}

function ShowForm: Integer ; stdcall ;

Form: TDLLForm;

begin

Form:= TDLLForm.Create (Application) ;

Result:= Form.ShowModal ;

Form.Free ;

end ;

begin

end .


unit TestAppUnit;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls;

type

TForm1 = class (TForm)

Button1: TButton;

procedure Button1Click(Sender: TObject ) ;

private

{ Private declarations }

public

{ Public declarations }

end ;

Form1: TForm1;

function ShowForm: Integer ; stdcall ;

External "dllforms.dll" ;

implementation

{$R *.DFM}

procedure TForm1.Button1Click (Sender: TObject ) ;

begin

end ;

end .

Прошу заметить, что при экспорте функции также было использовано ключевое слово stdcall.
Следует обратить особое внимание на работу с дочерними формами в DLL. Если, к примеру,
в вызывающем приложении главная форма имеет значение свойства FormStyle, равным MDIForm,
то при попытке вызова из DLL MDIChild-формы, на экране появится сообщение об ошибке,
в котором будет говориться, что нет ни одной активной MDI-формы.
В тот момент, когда вы пытаетесь показать ваше дочернее окно, VCL проверяет корректность
свойства FormStyle главной формы приложения. Однако в нашем случае все вроде бы верно.
Так в чем же дело? Проблема в том, что при проведении такой проверки, рассматривается объект Application,
принадлежащий не вызывающему приложению, а собственно динамической библиотеке.
Ну, и естественно, поскольку в DLL нет главной формы, проверка выдает ошибку.
Для того чтобы избежать такой ситуации, надо назначить объекту Application динамической библиотеки
объект Application вызывающего приложения. Естественно, это заработает только в том случае,
когда вызывающая программа - VCL-приложение. Кроме того, перед выгрузкой библиотеки из памяти
необходимо вернуть значение объекта Application библиотеки в первоначальное состояние.
Это позволит менеджеру памяти очистить оперативную память, занимаемую библиотекой.
Следовательно, вам нужно сохранить указатель на «родной» для библиотеки объект Application
в глобальной переменной, которая может быть использована при восстановлении его значения.
Итак, вернемся немного назад и перечислим шаги, необходимые нам для работы с помещенным
в DLL MDIChild-формами.
В динамической библиотеке создаем глобальную переменную типа TApplication.
Сохраняем указатель на объект Application DLL в глобальной переменной.
Объекту Application динамической библиотеки ставим в соответствие указатель на Application
вызывающего приложения.
Создаем MDIChild-форму и работаем с ней.
Возвращаем в первоначальное состояние значение объекта Application динамической библиотеки
и выгружаем DLL из памяти.
Первый шаг прост. Просто помещаем следующий код в верхней части модуля DLL:
var
DllApp: TApplication;
Затем создаем процедуру, которая будет изменять значение объекта Application и создавать дочернюю форму.
Процедура может выглядеть примерно так:

procedure ShowMDIChild(MainApp: TApplication) ;

Child: TMDIChild;

begin

if not Assigned (DllApp) then begin

DllApp:= Application;

Application:= MainApp;

end ;

Child:= TMDIChild.Create (Application.MainForm ) ;

Child.Show ;

end ;

Все, что нам теперь необходимо сделать, - это предусмотреть возвращение значения объекта Application
в исходное состояние. Делаем это с помощью процедуры MyDllProc:

procedure MyDLLProc(Reason: Integer ) ;

begin

if Reason = DLL_PROCESS_DETACH then

{ DLL is выгружается. Восстанавливаем значение указателя Application}

if Assigned (DllApp) then

Application:= DllApp;

end ;

Вместо заключения.
Использование динамически подключаемых библиотек не так сложно, как это может показаться на первый взгляд.

Использование динамически подключаемых библиотек не так сложно, как это может показаться на первый взгляд.
DLL предоставляют широчайшие возможности для оптимизации работы приложений,
а также работы самих программистов. Используйте DLL и, возможно, ваша жизнь станет легче!
http://subscribe.ru/
E-mail: [email protected] Поиск
на АПОРТ на Subscribe.Ru

Чтобы создать новую библиотеку DLL в Delphi, выберите команду меню File > New > Other . В панели Items Categories окна New Items выберите узел Delphi Projects , после чего дважды щелкните на элементе Dynamic-link Library в правой панели окна.

Мастер DLL Wizard создает главный файл исходного кода библиотеки DLL, который выглядит практически так же, как и исходный код, сгенерированный для обычного приложения. Единственное отличие состоит в том. что этот файл начинается с зарезервированного слова library , а не program .

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 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. } { Важное замечание относительно управления памятью библиотеки DLL: модуль ShareMem должен быть первым модулем в операторе uses вашей библиотеки и в операторе uses вашего проекта (выберите команду меню Project -> View Source (Проект -> Показать исходный код)), если ваша библиотека DLL экспортирует какие-либо процедуры или функции, передающие строки в качестве параметров или результатов выполнения функций. Это относится ко всем строкам, передаваемым или получаемым из вашей библиотеки DLL, и даже к тем строкам, которые вложены в записи и классы. Модуль ShareMem является модулем интерфейса для администратора общей памяти BORLNDMM.DLL, который вы должны развертывать вместе со своей библиотекой DLL. Чтобы не использовать BORLNDMM.DLL, передавайте строковую информацию с помощью параметров PChar или ShortString. } uses SysUtils, Classes; {$R *.res} begin end.

Все, что вам нужно сделать сейчас - это добавить подпрограмму перед блоком begin-end , вот и все. После этого вы получите внутреннюю подпрограмму, которую можно будет использовать в библиотеке DLL, но не во внешних приложениях. Если вы хотите вызывать подпрограмму из других приложении и других библиотек DLL, ее потребуетсл экспортировать. Чтобы экспортировать подпрограмму по имени, добавьте ее в список exports . Список exports имеет такой же синтаксис, что и список uses , за исключением того, что в списке exports любой элемент является подпрограммой, а не модулем.

Список exports обычно ставится непосредственно перед блоком begin-end . Взгляните на листинг 1, в котором представлен исходный код простой библиотеки FirstLib.dll , экспортирующей одну функцию.

Листинг 1. Простая библиотека DLL

Library FirstLib; function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; begin Result:= Num1; if Num2 > Result then Result:= Num2; if Num3 > Result then Result:= Num3; end; { Экспортируем функцию Max3 } exports Max3; begin end.

Когда вы добавите подпрограмму в список exports , вы тем самым экспортируете подпрограмму по ее имени. Вы можете также экспортировать подпрограмму под другим именем с помощью директивы name или посредством порядкового значения, используя директиву index . Однако применять директиву index не рекомендуется.

Ниже показан пример экспортирования директивы посредством порядкового значения или под другим именем:

Exports Max3 name "MyMax3Function", SaySomething index 1;

Статическая загрузка является самым простым из двух возможных способов загрузки библиотеки DLL. Статическую загрузку называют еще динамическим подключением во время загрузки (load-time dynamic linking ), потому что используемые библиотеки DLL автоматически загружаются во время запуска приложения.

Чтобы статически загрузить библиотеку DLL, необходимо скопировать объявление подпрограммы и вызывающее приложение и пометить ее с помощью директивы external , которая сообщает компилятору о том, что подпрограмма находится либо в объектном файле, либо в библиотеке DLL.

Когда вы импортируете подпрограммы из библиотеки DLL, вы должны помечать их директивой external , указывая вслед за ней имя библиотеки DLL, в которой содержится реализация подпрограммы. Далее показан пример импорта функции МахЗ из библиотеки FirstLib.dll :

Function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; external "FirstLib.dll";

При желании можете даже переименовать подпрограмму во время ее импорта. Для этого необходимо объявить подпрограмму под другим именем, а в конце объявления указать первоначальное имя с помощью директивы name :

Function Max(Num1, Num2, Num3: Integer): Integer; stdcall; external "FirstLib.dll" name "Max3";

Вы можете также импортировать функцию из библиотеки DLL. Для этого нужно создать модуль импорта и написать стандартный заголовок подпрограммы в разделе interface , а ее внешнюю реализацию - в разделе implementation этого модуля. В листинге 2 показан полный код модуль импорта библиотеки FirstLib.dll .

Листинг 2. Модуль импорта библиотеки FirstLib.dll.

Unit FirstLibInf; interface function Max3(Num1, Num2, Num3: Integer): Integer; stdcall; implementation const FirstLib = "FirstLib.dll"; {Компилятору сообщается о том, что реализация функции Max3 находится в библиотеке FirstLib.dll} function Max3; external FirstLib; end.

После того как вы создадите библиотеку DLL и ее модуль импорта, протестируйте библиотеку DLL, чтобы убедиться в том. что подпрограмма работает нормально. Поскольку запустить саму библиотеку DLL нельзя, нужно создать тестовое приложение, которое будет обращаться к библиотеке DLL. Быстрее всего сделать это можно, если создать проектную группу, добавив новый проект в текущий проект. Для этого потребуется щелкнуть правой кнопкой мыши на элементе ProjectGroup1 в окне Project Manager (Диспетчер проекта) и выбрать в контекстном меню команду Add New Project , как показано на рис. 1.

Листинг 3. Проверка подпрограммы Max3, импортированной из библиотеки FirstLib.dll.

Unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, FirstLibInf, StdCtrls; type TMainForm = class(TForm) Edit1: TEdit; Edit2: TEdit; Edit3: TEdit; Label1: TLabel; Label2: TLabel; Label3: TLabel; Max3Button: TButton; procedure Max3ButtonClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var MainForm: TMainForm; implementation {$R *.dfm} procedure TMainForm.Max3ButtonClick(Sender: TObject); var LargestNumber: Integer; begin LargestNumber:= Max3(StrToInt(Edit1.Text), StrToInt(Edit2.Text), StrToInt(Edit3.Text)); MessageDlg(Format("Наибольшее число: %d.", ), mtInformation, , 0); end; end.

Используемая литература: Внутренний мир Borland Delphi 2006. Иван Хладни.

В связи с бурным развитием технологий программирования, все больше людей сталкиваются с проблемой наращивания возможностей своих программ. Данная статья посвящена именно этому вопросу, а именно - программирование DLL в Borland Delphi. Кроме того, так как мы затронем вопросы по использованию библиотек DLL, то попутно коснемся импортирования функций из чужих DLL (в том числе и системных, т.е. WinAPI).

Области применения DLL

Итак, зачем же нужны библиотеки DLL и где они используются?.. Перечислим лишь некоторые из областей их применения:

  • Отдельные библиотеки , содержащие полезные для программистов дополнительные функции. Например, функции для работы со строками, или же - сложные библиотеки для преобразования изображений.
  • Хранилища ресурсов . В DLL можно хранить не только программы и функции, но и всевозможные ресурсы - иконки, рисунки, строковые массивы, меню, и т.д.
  • Библиотеки поддержки . В качестве примера можно привести библиотеки таких известных пакетов, как: DirectX , ICQAPI (API для ICQ), OpenGL и т.д.
  • Части программы . Например, в DLL можно хранить окна программы (формы), и т.п.
  • Плагины (Plugins). - Вот где настоящий простор для мыслей программиста! Плагины - дополнения к программе, расширяющие ее возможности. Например, в этой статье мы рассмотрим теорию создания плагина для собственной программы.
  • Разделяемый ресурс . DLL (Dynamic Link Library ) может быть использована сразу несколькими программами или процессами (т.н. sharing - разделяемый ресурс)

Краткое описание функций и приемов для работы с DLL

Итак, какие же приемы и функции необходимо использовать, чтобы работать с DLL? Разберем два метода импортирования функций из библиотеки:

1 способ . Привязка DLL к программе. Это наиболее простой и легкий метод для использования функций, импортируемых из DLL. Однако (и на это следует обратить внимание) этот способ имеет очень весомый недостаток - если библиотека, которую использует программа, не будет найдена, то программа просто не запустится, выдавая ошибку и сообщая о том, что ресурс DLL не найден. А поиск библиотеки будет вестись: в текущем каталоге, в каталоге программы, в каталоге WINDOWS\SYSTEM, и т.д.
Итак, для начала - общая форма этого приема:

implementation
...
function FunctionName(Par1: Par1Type; Par2: Par2Type; ...): ReturnType; stdcall ; external "DLLNAME.DLL" name "FunctionName" index FuncIndex;
// или (если не функция, а процедура):
procedure ProcedureName(Par1: Par1Type; Par2: Par2Type; ...); stdcall ; external "DLLNAME.DLL" name "ProcedureName" index ProcIndex;

Здесь: FunctionName (либо ProcedureName ) - имя функции (или процедуры), которое будет использоваться в Вашей программе;
Par1, Par2, ... - имена параметров функции или процедуры;
Par1Type, Par2Type, ... - типы параметров функции или процедуры (например, Integer );
ReturnType - тип возвращаемого значения (только для функции);
stdcall - директива, которая должна точно совпадать с используемой в самой DLL;
external "DLLNAME.DLL" - директива, указывающая имя внешней DLL, из которой будет импортирована данная функция или процедура (в данном случае - DLLNAME.DLL );
name "FunctionName" ("ProcedureName") - директива, указывающая точное имя функции в самой DLL. Это необязательная директива, которая позволяет использовать в программе функцию, имеющую название, отличное от истинного (которое она имеет в библиотеке);
index FunctionIndex (ProcedureIndex) - директива, указывающая порядковый номер функции или процедуры в DLL. Это также необязательная директива.

2 способ . Динамическая загрузка DLL. Это гораздо более сложный, но и более элегантный метод. Он лишен недостатка первого метода. Единственное, что неприятно - объем кода, необходимого для осуществления этого приема, причем сложность в том, что функция, импортируемая из DLL достуна лишь тогда, когда эта DLL загружена и находится в памяти... С примером можно ознакомиться ниже, а пока - краткое описание используемых этим методом функций WinAPI:

LoadLibrary (LibFileName: PChar ) - загрузка указанной библиотеки LibFileName в память. При успешном завершении функция возвращает дескриптор (THandle ) DLL в памяти.
GetProcAddress (Module: THandle ; ProcName: PChar ) - считывает адpес экспоpтиpованной библиотечной функции. При успешном завершении функция возвращает дескриптор (TFarProc ) функции в загруженной DLL.
FreeLibrary (LibModule: THandle ) - делает недействительным LibModule и освобождает связанную с ним память. Следует заметить, что после вызова этой процедуры функции данной библиотеки больше недоступны.

Практика и примеры

Ну а теперь пора привести пару примеров использования вышеперечисленных методов и приемов:

Теперь то же самое, но вторым способом - с динамической загрузкой:

{... Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

var
Form1: TForm1;
GetSimpleText: function (LangRus: Boolean): PChar;
LibHandle: THandle;

procedure Button1Click(Sender: TObject);
begin
{"Чистим" адрес функции от "грязи"}
@GetSimpleText:= nil;
{Пытаемся загрузить библиотеку}
LibHandle:= LoadLibrary("MYDLL.DLL");
{Если все OK}
if LibHandle >= 32 then begin
{...то пытаемся получить адрес функции в библиотеке}
@GetSimpleText:= GetProcAddress(LibHandle,"GetSimpleText");
{Если и здесь все OK}
if @GetSimpleText <> nil then
{...то вызываем эту функцию и показываем результат}
ShowMessage(StrPas(GetSimpleText(True)));
end;
{И не забываем освободить память и выгрузить DLL}
FreeLibrary(LibHandle);
end;

ПРИМЕЧАНИЕ : Следует воздерживаться от использования типа string в библиотечных функциях, т.к. при его использовании существуют проблемы с "разделением памяти". Подробней об этом можно прочитать (правда, на английском) в тексте пустого проекта DLL, который создает Delphi (File -> New -> DLL). Так что лучше используйте PChar, а затем при необходимости конвертируйте его в string функцией StrPas.

Ну а теперь разберем непосредственно саму библиотеку DLL:

Размещение в DLL ресурсов и форм

В DLL можно размещать не только функции, но и курсоры, рисунки, иконки, меню, текстовые строки. На этом мы останавливаться не будем. Замечу лишь, что для загрузки ресурса нужно загрузить DLL, а затем, получив ее дескриптор, - загружать сам ресурс соотвествующей функцией (LoadIcon, LoadCursor, и т.д.). В этом разделе мы лишь немного затронем размещение в библиотеках DLL окон приложения (т.е. форм в Дельфи).

Для этого нужно создать новую DLL и добавить в нее новую форму (File -> New -> DLL, а затем - File -> New Form). Далее, если форма представляет собой диалоговое окно (модальную форму (bsDialog)), то добавляем в DLL следующую функцию (допустим, форма называется Form1, а ее класс - TForm1):

Если же нужно разместить в DLL немодальную форму, то необходимо сделать две функции - открытия и закрытия формы. При этом нужно заставить DLL запомнить дескриптор этой формы.

Создание плагинов

Здесь мы не будем подробно рассматривать плагины, т.к. уже приведенные выше примеры помогут Вам легко разобраться в львиной части программирования DLL. Напомню лишь, что плагин - дополнение к программе, расширяющее ее возможности. При этом сама программа обязательно должна предусматривать наличие таких дополнений и позволять им выполнять свое предназначение.

Т.е., например, чтобы создать плагин к графическому редактору, который бы выполнял преобразование изображений, Вам нужно предусмотреть как минимум две функции в плагине (и, соответственно, вызвать эти функции в программе) - функция, которая бы возвращала имя плагина (и/или его тип), чтобы добавить этот плагин в меню (или в тулбар), плюс главная функция - передачи и приема изображения. Т.е. сначала программа ищет плагины, потом для каждого найденного вызывает его опозновательную функцию со строго определенным именем (например, GetPluginName) и добавляет нужный пункт в меню, затем, если пользователь выбрал этот пункт - вызывает вторую функцию, которой передает входное изображение (либо имя файла, содержащего это изображение), а эта функция, в свою очередь, обрабатывает изображение и возвращает его в новом виде (или имя файла с новым изображением). Вот и вся сущность плагина... :-)

Эпилог

В этой статье отображены основные стороны использования и создания библиотек DLL в Borland Delphi. Если у Вас есть вопросы - скидывайте их мне на E-mail: [email protected] , а еще лучше - пишите в конференции этого сайта, чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!

Карих Николай. Московская область, г.Жуковский

Аббревиатура DLL означает "динамически соединяемая библиотека". Dll в Delphi это файл, содержащий необходимые для работы компьютерной программы процедуры и функции, с которым программа соединяется на этапе выполнения.

Казалось бы, все необходимые подпрограммы можно описать в теле программы, зачем нужно создавать дополнительный dll-файл и соединяться с ним во время выполнения? Делается это для большей гибкости создаваемой программы. Ведь со временем могут измениться некоторые алгоритмы обработки данных. Если процедуры обработки будут содержаться в теле программы, её придётся перекомпилировать, и вновь передавать заказчикам достаточно объёмный файл. Передать же небольшой dll-файл, содержащий только несколько процедур, гораздо проще, и даже в автоматическом режиме.

Кроме того, работа библиотеки dll совершенно не зависит от языка программирования, на котором она будет создана. Потому создание dll для нашей программы мы можем поручить сторонним разработчикам, не думая о языках программирования которыми они владеют. Всё это, конечно, намного ускоряет создание и передачу заказчикам готового проекта

Создание DLL библиотеки

Создание dll в Delphi не сложнее создания дополнительного модуля. Выпоните команду File -> New -> -> Other... В появившепся диалоговом окне выберите пиктограмму DLL Wisard. В результате 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
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
.

Выберите имя для новой библиотеки dll и сохраните в отдельную папку, выполнив команду File -> Save As... В папке появятся 4 файла, среди которых собственно dll-файла не будет. Естественно, ведь это просто текстовые файлы, содержащие описание проекта. Для создания итогового файла библиотеки dll необходимо скомпилировать проект. Выполните команду Project -> Compile Project . В результате в нашей папке появится собственно dll файл, с которым и будет соединяться основная программа.

Пока это пустая библиотека.

...в настоящее время текст находится в стадии редактирования...

Устройства