СТАТЬЯ |
28.09.01
|
Разработка DLL в Borland Delphi
Статья была опубликована на сайте www.delphihelp.boom.ru
Вызов процедур и функций, загруженных из 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 из памяти. Ниже приведен код, демонстрирующий, как это можно сделать:
var
DLLInstance : THandle;
HelloWorld : THelloWorld;
begin
{ загружаем DLL }
DLLInstance := LoadLibrary(myfirstdll.dll');
{ получаем указатель }
@HelloWorld := GetProcAddress(DLLInstance, HelloWorld');
{ вызываем процедуру на выполнение }
HelloWorld(Self);
{ выгружаем DLL из оперативной памяти }
FreeLibrary(DLLInstance);
end;
Как уже говорилось выше, одним из недостатков статической загрузки DLL является невозможность продолжения работы приложения при отсутствии одной или нескольких библиотек. В случае с динамической загрузкой у вас появляется возможность программно обрабатывать такие ситуации и не допускать, чтобы программа «вываливалась» самостоятельно. По возвращаемому функциями LoadLibrary и GetProcAddress значениям можно определить, успешно ли прошла загрузка библиотеки и найдена ли в ней необходимая приложению процедура. Приведенный ниже код демонстрирует это.
procedure TForm1.DynamicLoadBtnClick(Sender: TObject);
type
THelloWorld = procedure(AForm : TForm);
var
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 . После этого вы увидите примерно следующий код:
library Project2;
{здесь были комментарии}
uses
SysUtils,
Classes;
{$R *.RES}
begin
end.
Сохраните полученный проект. Назовем его DllForms.dpr.
Теперь следует создать новую форму. Это можно сделать по-разному. Например, выбрав пункт меню File|New Form. Добавьте на форму какие-нибудь компоненты. Назовем форму DllForm и сохраним получившийся модуль под именем DllFormUnit.pas .
Вернемся к главному модулю проекта и поместим в него функцию ShowForm, в задачу которой будет входить создание формы и ее вывод на экран. Используйте для этого приведенный ниже код.
function ShowForm : Integer; stdcall;
var
Form : TDLLForm;
begin
Form := TDLLForm.Create(Application);
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
SysUtils,
Classes,
Forms,
DllFormUnit in 'DllFormUnit.pas' {DllForm};
{$R *.RES}
function ShowForm : Integer; stdcall;
var
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;
var
Form1: TForm1;
function ShowForm : Integer; stdcall;
external dllforms.dll';
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowForm;
end;
end.
Прошу заметить, что при экспорте функции также было использовано ключевое слово stdcall.
Следует обратить особое внимание на работу с дочерними формами в DLL. Если, к примеру, в вызывающем приложении главная форма имеет значение свойства FormStyle, равным MDIForm, то при попытке вызова из DLL MDIChild-формы, на экране появится сообщение об ошибке, в котором будет говориться, что нет ни одной активной MDI-формы.
В тот момент, когда вы пытаетесь показать ваше дочернее окно, VCL проверяет корректность свойства FormStyle главной формы приложения. Однако в нашем случае все вроде бы верно. Так в чем же дело? Проблема в том, что при проведении такой проверки, рассматривается объект Application, принадлежащий не вызывающему приложению, а собственно динамической библиотеке. Ну, и естественно, поскольку в DLL нет главной формы, проверка выдает ошибку. Для того чтобы избежать такой ситуации, надо назначить объекту Application динамической библиотеки объект Application вызывающего приложения. Естественно, это заработает только в том случае, когда вызывающая программа - VCL-приложение. Кроме того, перед выгрузкой библиотеки из памяти необходимо вернуть значение объекта Application библиотеки в первоначальное состояние. Это позволит менеджеру памяти очистить оперативную память, занимаемую библиотекой. Следовательно, вам нужно сохранить указатель на «родной» для библиотеки объект Application в глобальной переменной, которая может быть использована при восстановлении его значения.
Итак, вернемся немного назад и перечислим шаги, необходимые нам для работы с помещенным в DLL MDIChild-формами.
var
DllApp : TApplication;
Затем создаем процедуру, которая будет изменять значение объекта Application и создавать дочернюю форму. Процедура может выглядеть примерно так:
procedure ShowMDIChild(MainApp : TApplication);
var
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 и, возможно, ваша жизнь станет легче!
Дополнительную информацию Вы можете получить в компании Interface Ltd.
Отправить
ссылку на страницу по e-mail
Обсудить на форуме
Interface Ltd. Отправить E-Mail http://www.interface.ru |
|
Ваши замечания и предложения
отправляйте автору По техническим вопросам обращайтесь к вебмастеру Документ опубликован: 28.09.01 |