Средства разработки приложений

Вызов процедур и функций, загруженных из 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.
Причем создание и помещение форм в динамическую библиотеку не слишком сильно отличается от работы с формами в обычном проекте. Сначала мы рассмотрим, каким образом можно написать библиотеку, содержащую формы, а затем мы поговорим об использовании технологии 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-формами.
  1. В динамической библиотеке создаем глобальную переменную типа TApplication.
  2. Сохраняем указатель на объект Application DLL в глобальной переменной.
  3. Объекту Application динамической библиотеки ставим в соответствие указатель на Application вызывающего приложения.
  4. Создаем MDIChild-форму и работаем с ней.
  5. Возвращаем в первоначальное состояние значение объекта Application динамической библиотеки и выгружаем DLL из памяти.
Первый шаг прост. Просто помещаем следующий код в верхней части модуля DLL: 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;

Содержание раздела