На этом шаге мы рассмотрим экспортирование дочерних форм из DLL.
Дочерние формы (формы, которые в качестве родителей имеют другие формы, а не экран компьютера) достаточно распространены в приложениях. В принципе, такие формы могут поставляться и в DLL. При этом используется статическая загрузка DLL. Динамическая загрузка DLL возможна только в том случае, если библиотека загружается сразу же вместе с созданием формы, на которой размещается дочерняя форма, и выгружается при закрытии этой формы.
Следует принять во внимание тот факт, что дочерняя форма может быть задействована в нескольких формах главного приложения с различающимся жизненным циклом. Поэтому помимо функции, которая будет создавать дочернюю форму, необходимо иметь функцию для ее разрушения и возврата занятых ресурсов операционной системе. При этом опять же, поскольку возможно существование нескольких дочерних форм, при создании каждой формы главному приложению должен быть сообщен ее идентификатор, который необходимо сохранить и использовать при вызове деструктора данной дочерней формы. Исходя из этих теоретических предпосылок, можно приступить к созданию библиотеки, способной экспортировать дочерние формы. Приведем полный текст DLL (она содержит одну форму Form1):
library ChildDLL; uses SysUtils, Forms, Types, Classes, Windows, Dialogs, UnitDLLForm1 in 'UnitDLLForm1.pas' {Form1}; {$R *.res} function SetParent(hWndChild: HWnd; hWndNewParent: HWnd):HWnd; stdcall; external 'user32.dll' name 'SetParent'; function CreateCustomWindow(ParentHandle:integer; DataRect:TRect; var WinHandle:THandle):integer; stdcall; export; //Метод возвращает дескриптор формы, который //должен быть передан деструктору var FD:TForm1; begin Result := 0; WinHandle := 0; try FD := TForm1.Create(nil); Result := integer(FD); WinHandle := FD.Handle; if ParentHandle<>0 then begin SetParent(WinHandle,ParentHandle); with FD do begin SetWindowPos(Handle, HWND_TOP, DataRect.Left, DataRect.Top, DataRect.Right-DataRect.Left, DataRect.Bottom-DataRect.Top, SWP_SHOWWINDOW); Show; end; end; except On E:exception do MessageDlg(E.Message,mtError,[mbOK],0); end; end; procedure DeleteCustomWindow(WinID:integer); stdcall; export; begin try if WinID<>0 then TForm1(WinID).Free; except On E:exception do MessageDlg(E.Message,mtError,[mbOK],0); end; end; exports CreateCustomWindow, DeleteCustomWindow; begin end.
На форму TForm1 помещают элементы управления, которые необходимо пока-зать в главном приложении (рисунок 1). Обычно свойство BorderStyle дочерних форм устанавливают равным bsNone.
Рис.1.Форма в виде DLL, экспортируемая как дочерняя
Далее, дочерним формам в качестве параметров функции следует передавать прямоугольную область родительского окна, в которой они размещаются. Это означает, что дочерняя форма должна иметь хорошо отлаженные обработчики событий, которые связаны с изменением ее размеров. Как и все предыдущие примеры, код показа дочерних форм следует сделать языково-независимым - а это значит, что необходимо использовать функции Windows API для изменения свойств их родителей. Такая функция определена в модуле user32.dll - SetParent. Другая функция Windows API - SetWindowPos - требуется для изменения границ формы. Возвращаемым параметром является указатель на объект. Однако задействовать его сразу приложение не может - оно должно запомнить этот указатель и использовать при вызове функции DeleteCustomWindow, - поэтому сохраняется возможность применения данного проекта в приложениях, написанных не на Delphi.
Еще один полезный параметр - дескриптор окна формы - передается как вариантный параметр функции CreateCustomWindow. Он может быть использован главным приложением для посылки сообщений форме, динамического изменения размеров, атрибутов видимости и т.д. посредством вызова соответствующих функций Windows API.
Код основного приложения для тестирования данной DLL выглядит следующим образом (приведем полный текст модуля):
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs; type TForm2 = class(TForm) procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } FHLib:THandle; FChildID:integer; FChildHandle:THandle; public { Public declarations } end; var Form2: TForm2; implementation {$R *.dfm} type TCreateCustomWindow=function(ParentHandle:integer; DataRect:TRect; var WinHandle:THandle):integer; stdcall; TDeleteCustomWindow=procedure(WinID:integer); stdcall; procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction); begin Action:=caFree; end; procedure TForm2.FormCreate(Sender: TObject); var CreateW:TCreateCustomWindow; begin FHLib:=LoadLibrary('ChildDLL.dll'); if FHLib<>0 then begin CreateW:=GetProcAddress(FHLib,'CreateCustomWindow'); if Assigned(CreateW) then FChildID:=CreateW(Handle,ClientRect,FChildHandle); end; end; procedure TForm2.FormDestroy(Sender: TObject); var DeleteW:TDeleteCustomWindow; begin if (FChildID>0) and (FHLib<>0) then begin DeleteW:=GetProcAddress(FHLib,'DeleteCustomWindow'); if Assigned(DeleteW) then DeleteW(FChildID); end; if FHLib<>0 then FreeLibrary(FHLib); end; end.
На форму TForm2 в этом проекте никаких элементов управления не помещается. Главная форма приложения (на рисунке 2 форма Form1) содержит одну кнопку, при щелчке на которой происходит немодальное отображение формы TForm2:
with TForm2.Create(Self) do Show;
При создании второй формы выполняется загрузка DLL, и форма, созданная в DLL, становится дочерней для TForm2. Можно создать несколько экземпляров TForm2. При разрушении конкретного экземпляра разрушается и дочернее окно на нем - для этого используется сохраненный ранее параметр FChildlD.
Рис.2. Отображение в главном приложении дочерней формы, экспортируемой из DLL
Казалось бы, аналогичную методику можно использовать для экспорта из DLL других элементов управления, среди предков которых нет класса TForm. Однако при попытке вызвать функцию SetParent непосредственно для элемента управления происходит генерация исключения, связанного с отсутствием родительского окна у элемента управления, и в результате он не отображается на форме.
В COM DLL часто используют дочерние формы. Однако код их создания абсолютно не похож на представленный здесь. Дочерние формы COM DLL - это элементы управления ActiveX.
На следующем шаге мы рассмотрим реализацию внутрипроцессного сервера автоматизации.