Отправляет email-рассылки с помощью сервиса Sendsay

Программирование на Delphi

  Все выпуски  

Программирование на DELPHI v3-7 Всё о ДЛЛ(Часть 2)


Информационный Канал Subscribe.Ru

Программирование на Delphi
подписчиков: +8500

www.goldfaq.ru   forum.goldfaq.ru

А это вторая часть рассылки.

Delphi-Статьи

Вызов методов приложения в DLL

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

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

var

  NSum:integer=0; 

function CalculateSum(ReturnCallback:pointer):integer; stdcall; 

   external 'FirstLib.dll' name 'Sum'; 

function GetNextValue:integer; stdcall; 

begin 

  if NSum<200 then begin 

    Inc(NSum); 

    Result:=NSum; 

  end else Result:=-1; 

end; 

procedure TForm1.Button9Click(Sender: TObject); 

begin 

  Caption:=IntToStr(CalculateSum(@GetNextValue)); 

end; 

В приложении создается метод (не метод класса!), адрес которого передается в DLL. Для того чтобы данный метод можно было вызывать из DLL, созданных на других языках программирования, желательно использовать соглашение stdcall вызова при реализации  указанных методов (так называемые callback-методы). Использование вызова метода в DLL можно проиллюстрировать на таком примере:

type

  TReturnNextMethod=function:integer; stdcall; 

function CalculateSum(ReturnCallback:pointer):integer; stdcall; export; 

var 

  N:integer; 

  ReturnNext:TReturnNextMethod; 

begin 

  Result:=0; 

  if ReturnCallback=nil then Exit; 

  N:=0; 

  ReturnNext:=TReturnNextMethod(ReturnCallback); 

  while N>=0 do begin 

    N:=ReturnNext; 

    if N>=0 then Result:=Result+N; 

  end; 

end; 

При необходимости вызвать метод объекта следует учитывать, что данный метод объекта характеризуется двумя адресами — адресом метода и адресом данных. Следовательно, необходимо передавать два указателя. Но вместо передачи двух указателей можно воспользоваться структурой TMethod, определенной в модуле SysUtils.pas:

type

  TMethod = record 

    Code, Data: Pointer; 

  end; 

Код в приложении для приведенного выше примера выглядит так:

type

  TGetNextValueObject=function:integer of object; stdcall; 

function CalculateSumObject(Method:TMethod):integer; stdcall; 

   external 'FirstLib.dll' name 'SumObject'; 

function TForm1.GetNextValueObject:integer; stdcall; 

begin 

  if NSum<10 then begin 

    Inc(NSum); 

    Result:=NSum; 

  end else Result:=-1; 

end; 

procedure TForm1.Button10Click(Sender: TObject); 

var 

  FGetNext:TGetNextValueObject; 

begin 

  NSum:=0; 

  FGetNext:=GetNextValueObject; 

  Caption:=IntToStr(CalculateSumObject(TMethod(FGetNext))); 

end; 

Код в DLL, осуществляющий вызов метода объекта, выглядит следующим образом:

type

  TReturnNextMethodObject=function:integer of object; stdcall; 

function CalculateSumObject(Method:TMethod):integer; stdcall; export; 

var 

  N:integer; 

  ReturnNext:TReturnNextMethodObject; 

begin 

  Result:=0; 

  N:=0; 

  ReturnNext:=TReturnNextMethodObject(Method); 

  while N>=0 do begin 

    N:=ReturnNext; 

    if N>=0 then Result:=Result+N; 

  end; 

end; 

Следует учитывать, что методы объекта являются зависимыми от языка, то есть в разных языках программирования генерируются различные способы передачи данных в метод объекта. Поэтому данный пример следует использовать только в случае, если и приложение, и DLL написаны на Delphi (или на одном и том же языке программирования).

И наконец, само приложение может экспонировать методы таким же способом, что и DLL. В приложении можно создать секцию exports и объявить имена (и/или индексы) методов. После этого в DLL можно воспользоваться методом GetProcAddress для получения указателя на метод и вызвать его. Для описанного выше примера код приложения будет такой:

function GetNextValueExport:integer; stdcall; export;

begin 

  if NSum<10 then begin 

    Inc(NSum); 

    Result:=NSum; 

  end else Result:=-1; 

end; 

function CalculateSumExport(HInstance:integer; MethodName:pchar):integer; stdcall;

external 'FirstLib.dll' name 'SumExport';

procedure TForm1.Button11Click(Sender: TObject); 

var 

  N:integer; 

begin 

  NSum:=0; 

  N:=CalculateSumExport(HInstance,'GetNextValueExport'); 

  Caption:=IntToStr(N); 

end; 

exports {!! The section is announced in Application !!} 

  GetNextValueExport index 1 name 'GetNextValueExport'; 

Обратите внимание: теперь в приложении (в проекте, где будет генерироваться *.exe-файл[U2] [NE3] ) определена секция exports! Соответствующий код в DLL для тестирования данного метода выглядит следующим образом:

type

  TReturnNextMethodExport=function:integer; stdcall; 

function CalculateSumExport(AppInstance:integer; MethodName:pchar):integer; stdcall; export; 

var 

  ReturnNextExport:TReturnNextMethodExport; 

  N:integer; 

begin 

  Result:=0; 

  N:=0; 

  ReturnNextExport:=GetProcAddress(AppInstance,MethodName); 

  while N>=0 do begin 

    N:=ReturnNextExport; 

    if N>=0 then Result:=Result+N; 

  end; 

end; 

При запуске этого проекта приложение автоматически загружает DLL и находит адрес метода в DLL CalculateNextExport (который экспортируется по имени SumExport). Загруженная DLL в качестве параметров получает заголовок модуля приложения и имя метода, под которым [U4] он экспортируется в приложении. Далее DLL использует метод GetProcAddress для нахождения адреса метода, экспортируемого приложением. Для того чтобы был возвращен адрес метода, в приложении объявляется секция exports, где описано внешнее имя метода. Этот пример показывает формально одинаковые структуру и способ формирования *.exe- и *.dll-файлов.

 

Модальные формы в DLL

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

function ExecDialog(AppHandle:THandle; var PictName:pchar):boolean; stdcall; export;

var 

  FDialog:TForm1; 

begin 

  FDialog:=nil; 

  PictName:=nil; 

  Result:=False; 

  Application.Handle:=AppHandle; {Two icons are arisen at taskbar without this operator.

                                       Warning while dynamic loading!} 

  try 

    FDialog:=TForm1.Create(Application); 

    if FDialog.ShowModal=mrOK then begin 

      FillMemory(@C[0],1000,0); 

      if length(FDialog.Edit1.Text)>0 then 

      StrPCopy(C,FDialog.Edit1.Text); 

      PictName:=@C[0]; 

      Result:=True; 

    end; 

    FDialog.Release; 

    FDialog:=nil; 

    {!!! Case dynamic loading, one has to use method Free instead of Release!} 

  except 

    On E:exception do begin 

      ShowMessage(E.Message); 

      if Assigned(FDialog) then FDialog.Release; 

    end; 

  end; 

end; 

Данный код вызова диалога следует применять только при статической загрузке DLL. При выполнении диалога в DLL нужно учитывать, что формы в DLL не могут создаваться вместе с запуском DLL, как это возможно в приложении при установке опции Auto-Create Form в разделе Project/Options/Forms. Поэтому форму необходимо создавать динамически, вызывая ее конструктор Create из кода. Соответственно перед выходом из процедуры, вызывающей форму, необходимо вызвать ее деструктор (в нашем примере — FDialog.Release). Необходимо учитывать, что в DLL создается объект типа TApplication. Поскольку и само приложение имеет данный объект, то (если не принимать никаких мер) на экране в панели приложений появятся две пиктограммы: одна — для приложения, другая — для DLL, выполняющей диалог

При этом после нажатия на пиктограмму приложения оно активируется и всплывает главная форма, но доступ к элементам управления главной формы получить нельзя. Ясно, что такое поведение является некорректным. Поэтому в качестве параметра метода в DLL, выполняющего диалог, необходимо использовать ссылку на объект TApplication (точнее, его свойство Handle) приложения. Посредством присвоения Application.Handle:=AppHandle; в DLL уничтожается объект TApplication и приложение начинает поддержку рассылки сообщений элементам управления, созданным в DLL. В этом случае на панели задач присутствует одна пиктограмма приложения — это корректно. Приведем типичный пример кода основного приложения, вызывающего диалог из DLL:

function ExecDialog(AppHandle:THandle; var PictName:pchar):boolean; stdcall;

   external 'FirstLib.dll' name 'ExecDialog';

  

procedure TForm1.Button3Click(Sender: TObject); 

var 

  P:pchar; 

  S:string; 

begin 

  if ExecDialog(Application.Handle,P) then begin 

    S:=P; 

    Caption:=S; 

  end; 

end; 

При использовании динамической загрузки DLL, возникает ряд сложностей.

Первая заключается в том, что, как правило, загрузка и выгрузка библиотеки, в которой выполняется диалог, осуществяются в пределах одного метода:

HLib:=LoadLibrary('FirstLib.dll');

if HLib>HINSTANCE_ERROR then begin 

  ExecDialog:=GetProcAddress(HLib,'ExecDialog'); 

  if Assigned(ExecDialog) then begin 

    if ExecDialog(Application.Handle,P) then {...}; 

  end else ShowMessage('Method with name ExecuteDialog was not found'); 

   FreeLibrary(HLib); 

end else ShowMessage('Can not load library FirstLib.dll'); 

В этом случае библиотека выгружается немедленно после выполнения диалога, а метод Release, который рекомендуется использовать для вызова деструктора формы, посылает сообщения CM_RELEASE самой форме посредством вызова метода PostMessage. Этот метод ставит сообщение в конец очереди, приложение продолжает выполнять код — и выгружает DLL! Только после выполнения кода начинается обработка очереди сообщений, в конце концов достается сообщение CM_RELEASE, делается попытка выполнить деструктор формы — но методы-то уже выгружены! Если операционная система обладает значительным резервом ресурсов, то велика вероятность, что место в ОЗУ, где хранился код, сохранит свое содержимое и форма диалога будет разрушена успешно. Но при небольшом количестве ресурсов на освободившееся место в ОЗУ немедленно помещаются какие-либо данные из виртуальной дисковой памяти, а попытка выполнить деструктор кончается исключением. Поэтому при динамической загрузке обязательно нужно использовать метод Free вместо Release. Кроме того, перед выгрузкой DLL рекомендуется вызвать метод Application.ProcessMessages.

Вторая проблема состоит в следующем. Если использовать один и тот же объект TApplication в приложении и DLL посредством выполнения приведенного выше оператора —  Application.Handle:=AppHandle; — перед вызовом метода ShowModal в DLL, то после выгрузки DLL главная форма приложений минимизируется и ее необходимо восстанавливать вновь. Один из способов решения проблемы — вызвать метод ShowWindow(Handle,SW_RESTORE) сразу же после выполнения команды FreeLibrary в приложении. Однако при этом главная форма приложения будет мерцать. Другой способ — оставить разные объекты TApplication в приложении и DLL,— для этого не надо выполнять оператор Application.Handle:=AppHandle; в DLL. Но тогда на панели задач появляются две пиктограммы. Корректное решение проблемы — присвоить нулевое значение свойству Application.Handle в DLL перед выходом из метода. Ниже приведен рекомендуемый код в DLL, которая выполняет диалог при динамической загрузке:

function ExecDialog(AppHandle:THandle; var PictName:pchar):boolean; stdcall; export;

var 

  FDialog:TForm1; 

begin 

  FDialog:=nil; 

  PictName:=nil; 

  Result:=False; 

  Application.Handle:=AppHandle; 

  try 

    FDialog:=TForm1.Create(Application); 

    if FDialog.ShowModal=mrOK then begin 

      FillMemory(@C[0],1000,0); 

      if length(FDialog.Edit1.Text)>0 then 

      StrPCopy(C,FDialog.Edit1.Text); 

      PictName:=@C[0]; 

      Result:=True; 

    end; 

    FDialog.Free; 

    FDialog:=nil; 

  except 

    On E:exception do begin 

      ShowMessage(E.Message); 

      if Assigned(FDialog) then FDialog.Free; 

    end; 

  end; 

  Application.ProcessMessages; 

  Application.Handle:=0; 

end; 

Приведем код приложения, динамически вызывающего данную DLL:

type

  TExecDialog=function(AppHandle:THandle; var PictName:pchar):boolean; stdcall; 

  

procedure TForm1.Button4Click(Sender: TObject); 

var 

  ExecDialog:TExecDialog; 

  HLib:THandle; 

  P:pchar; 

  S:string; 

begin 

  HLib:=0; 

  try 

    HLib:=LoadLibrary('FirstLib.dll'); 

    if HLib>HINSTANCE_ERROR then begin 

      ExecDialog:=GetProcAddress(HLib,'ExecDialog'); 

      if Assigned(ExecDialog) then begin 

        if ExecDialog(Application.Handle,P) then begin 

          S:=P; 

          Caption:=S; 

        end; 

      end else ShowMessage('Method with name ExecuteDialog was not found'); 

    end else ShowMessage('Can not load library FirstLib.dll'); 

  finally 

    Application.ProcessMessages; 

    if HLib>HINSTANCE_ERROR then FreeLibrary(HLib); 

  end; 

end; 

Немодальные формы в DLL

Показ немодальных форм традиционно осуществляется со статической загрузкой DLL. Вот типичный код для показа немодальной формы в DLL:

procedure ShowNonModalForm(AppHandle:THandle); stdcall; export; 

begin 

  Application.Handle:=AppHandle; 

  with TForm1.Create(Application) do Show; 

end; 

Как и при показе модальных форм, необходимо присвоить дескриптор окна главного приложения приложению, создаваемому в DLL, — иначе на панели задач будут показаны две иконки. Затем просто создается форма и вызывается ее метод Show. Такой способ показа немодальных форм приводит к тому, что из главного приложения можно неоднократно вызвать данный метод и тем самым создать несколько экземпляров форм. Зачастую это оправданно, поскольку одинаковые типы форм могут содержать, например, разные документы. Но при этом способе показа рекомендуется сделать обработчик события OnClose для TForm1 и присвоить параметру CloseAction значение caFree — в противном случае при закрытии форма будет спрятана [NE5][U6]  на экране без освобождения системных ресурсов.

Для показа единственного экземпляра немодальной формы следует немного изменить код:

procedure ShowSingleNonModalForm(AppHandle:THandle); stdcall; export; 

begin 

  Application.Handle:=AppHandle; 

  if Assigned(Form2) then Form2.Show else begin 

    Form2:=TForm2.Create(Application); 

    Form2.Show; 

  end; 

end; 

Первоначально необходимо проверить, была ли создана форма ранее. Если была — просто вызывается ее метод Show; если нет — вызывается тот же метод после отработки конструктора. Вызов метода Show для уже созданного экземпляра формы имеет смысл, поскольку пользователь может обратиться к команде показа формы в тех случаях, когда уже имеющийся экземпляр перекрыт другими окнами и незаметен на экране, — использование команды Show приводит к всплытию формы. Переменная Form2 является глобальной переменной.

Оба описанных выше способа вызова немодальных форм не требуют создания специальной процедуры для их разрушения. Ресурсы будут корректно освобождены при закрытии приложения, так как приложение является владельцем форм. Код приложения для тестирования этих методов выглядит следующим образом:

procedure ShowNonModalForm(AppHandle:THandle); stdcall;

 external 'NMStat.dll' name 'ShowNonModalForm';

procedure ShowSingleNonModalForm(AppHandle:THandle); stdcall;

 external 'NMStat.dll' name 'ShowSingleNonModalForm'; 

  

procedure TForm1.Button1Click(Sender: TObject); 

begin 

  ShowNonModalForm(Application.Handle); 

end; 

  

procedure TForm1.Button2Click(Sender: TObject); 

begin 

  ShowSingleNonModalForm(Application.Handle); 

end; 

Иногда возникает необходимость показа немодальных форм из динамически загружаемых DLL, например при редком использовании в приложении немодальных форм для экономии ресурсов. Если реализовать код так же, как и при показе модальных диалогов, то форма будет создана и, может быть, даже показана на экране. Но после этого произойдет выгрузка DLL, а затем немедленно последуют исключения, поскольку в памяти компьютера будет отсутствовать код для работы с элементами управления формы. Традиционное решение этой проблемы выглядит следующим образом: загружается динамическая библиотека, в качестве одного из параметров передается адрес метода главного приложения, который будет вызван при закрытии немодальной формы — обычно в обработчике события OnDestroy. Этот метод должен информировать главное приложение о необходимости выгрузки DLL из памяти компьютера, но DLL должна выгружаться после завершения его работы (и, следовательно, после завершения работы деструктора формы) — иначе возможно исключение из-за отсутствия кода в памяти компьютера. Выгрузка DLL после завершения работы приложения [U7] достигается с использованием асинхронной развязки — посылки сообщения методом PostMessage какому-либо окну приложения, обычно главной форме. Приведем код реализации данной технологии в DLL:

type 

  TNotifyClose=procedure; stdcall; 

  

  TForm1 = class(TForm) 

    Memo1: TMemo; 

    procedure FormDestroy(Sender: TObject); 

    procedure FormClose(Sender: TObject; var Action: TCloseAction); 

  private 

    { Private declarations } 

  public 

    { Public declarations } 

    FNC:TNotifyClose; 

  end; 

  

var 

  Form1: TForm1; 

  

implementation 

  

{$R *.DFM} 

  

procedure TForm1.FormDestroy(Sender: TObject); 

begin 

  if Assigned(FNC) then FNC; 

end; 

  

procedure DynNonmodal(AppHandle:THandle; NC:pointer); stdcall; export; 

begin 

  Application.Handle:=AppHandle; 

  if Assigned(Form1) then Form1.Show else begin 

    Form1:=TForm1.Create(Application); 

    Form1.FNC:=TNotifyClose(NC); 

    Form1.Show; 

  end; 

end; 

Приложение, использующее эту DLL, имеет следующий код (WM_DLLUNLOAD определена как константа в секции interface модуля):

type 

  TDynNonmodal=procedure(AppHandle:THandle; NC:pointer); stdcall; 

  

procedure ReceiveCloseNotify; stdcall; 

begin 

  Application.ProcessMessages; 

  PostMessage(Form1.Handle,WM_DLLUNLOAD,0,0); 

end; 

  

procedure TForm1.WMDLLUnload(var Message:TMessage); 

begin 

  Application.ProcessMessages; 

  if FHLib>HINSTANCE_ERROR then FreeLibrary(FHLIB); 

  FHLib:=0; 

  ShowMessage('Library unloaded'); 

end; 

  

procedure TForm1.Button3Click(Sender: TObject); 

var 

  DM:TDynNonmodal; 

begin 

  if FHLIB<=HInstance_Error then FHLib:=LoadLibrary('NMDyn1.dll'); 

  if FHLib>HInstance_Error then begin 

    DM:=GetProcAddress(FHLib,'DynNonmodal'); 

    if Assigned(DM) then DM(Application.Handle,@ReceiveCloseNotify); 

  end; 

end; 

Очевидно, что код получается довольно громоздким: в главном приложении необходимо реализовывать три метода вместо одного. Альтернативный вариант можно предложить исходя из того, что в DLL имеется объект TApplication, который может поддерживать цикл выборки сообщений. Но в DLL нельзя создать форму, используя метод TApplication. CreateForm, так как соответствующая закладка диалога Project/Options/Forms отсутствует в проектах Delphi 4 и 5 и неактивна в Delphi 3. Однако можно вызвать все методы объекта Tapplication, вручную дописав соответствующий код в DLL:

procedure ShowNMApplication; stdcall; export; 

begin 

  if Assigned(Form1) then begin 

    Form1.Show; 

    Exit; 

  end else begin 

    Application.Initialize; 

    Application.CreateForm(TForm1, Form1); 

    Application.Run; 

    Form1.Free; 

    Form1:=nil; 

  end; 

end; 

Следует обратить внимание, что дескриптор главного приложения не присваивается в данном проекте дескриптору TApplication в DLL. Это реально приводит к появлению двух пиктограмм на панели приложений. Правда, в некоторых случаях это полезно — так легче добраться до перекрытых окон. Интересно отметить, что в Delphi 3 после написания данного кода становятся доступными элементы управления диалога Project/Options/Forms, где можно определить автоматически создаваемые формы и главную форму приложения. Код главного приложения, использующий данную DLL, таков:

type 

  TShowApp=procedure; stdcall; 

  

procedure TForm1.Button4Click(Sender: TObject); 

var 

  HLib:THandle; 

  ShowApp:TShowApp; 

begin 

  HLib:=LoadLibrary('NMDyn2.dll'); 

  if HLib>HINSTANCE_ERROR then begin 

    ShowApp:=GetProcAddress(HLib,'ShowNMApplication'); 

    if Assigned(ShowApp) then ShowApp; 

    Application.ProcessMessages; 

    FreeLibrary(HLib); 

  end; 

end; 

В отличие от предыдущего примера, динамическая загрузка DLL и ее выгрузка осуществляются в одном методе, да и объем написанного кода существенно меньше.

Экспорт дочерних форм из DLL

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

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

function CreateCustomWindow(ParentHandle:integer; DataRect:TRect;

                                 var WinHandle:THandle):integer; stdcall; export; 

{The method returns descriptor of the form which must be passed to destructor (below)} 

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; 

На форму TForm1 помещают элементы управления, которые необходимо показать в главном приложении. Обычно свойство BorderStyle дочерних форм устанавливают равным bsNone

Далее, дочерним формам в качестве параметров метода следует передавать прямоугольную область родительского окна, в которой она размещается. Это означает, что дочерняя форма должна иметь хорошо отлаженные обработчики событий, которые связаны с изменением ее размеров. Как и во всех предыдущих примерах, показ дочерних форм следует сделать независимым от языка; следовательно, необходимо использовать методы Windows API для изменения родителей. Такой метод определен в модуле user32.dll — SetParent. Другой метод Windows API — SetWindowPos — используется для изменения границ формы. В качестве возвращаемого параметра возвращается указатель на объект. Но приложение не будет его использовать, поскольку оно должно его запоминать и использовать при вызове метода DeleteCustomWindow.  Поэтому сохраняется возможность использовать данный проект в приложениях, написанных не на Delphi, а на других языках.

Еще один полезный параметр — дескриптор окна формы — передается как var-параметр метода CreateCustomWindow. Его может использовать главное приложение для посылки сообщений форме, динамического изменения размеров, видимости и т.д. посредством вызова соответствующих методов WinAPI.

Код основного приложения для тестирования данной DLL выглядит следующим образом:

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>HINSTANCE_ERROR 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>HINSTANCE_ERROR) then begin 

    DeleteW:=GetProcAddress(FHLib,'DeleteCustomWindow'); 

    if Assigned(DeleteW) then DeleteW(FChildID); 

  end; 

  if FHLib>HINSTANCE_ERROR then FreeLibrary(FHLib); 

end; 

В этом проекте на форму TForm2 никакие элементы управления не помещаются. Главная форма приложения содержит одну кнопку, при нажатии на которую выполняется немодальный показ TForm2:

with TForm2.Create(Self) do Show;

При создании второй формы происходит загрузка DLL, а форма, созданная в DLL, становится дочерней для TForm2. Можно создать несколько экземпляров TForm2. При разрушении конкретного экземпляра разрушается и дочернее окно на нем, для чего используется сохраненный ранее параметр FchildID

Казалось бы, аналогичную методику можно использовать для экспорта из DLL других элементов управления, среди предков которых не содержится класс TForm. Однако при попытке использовать метод SetParent непосредственно для элементов управления происходит генерация исключения об отсутствии у элемента управления родительского окна, и этот элемент [U8] не показывается на форме.

Заключение

Итак, с помощью динамически загружаемых библиотек можно оптимизировать ресурсы, необходимые для выполнения приложений; использовать в проектах модули, написанные на различных языках программирования; создавать проекты, которые могут иметь необязательные функции и пункты меню. Вызов методов из DLL не представляет трудностей, за исключением того, что следует обращать особое внимание на исключительные ситуации: не допускать попадания экземпляра — потомка Exception в главный модуль, обязательно вызывать команду FreeLibrary при наличии исключений. Анализу исключительных ситуаций и отладке приложений будет посвящена следующая статья данного цикла.

НОВЫЕ ОТВЕТЫ

 

НОВЫЕ ВОПРОСЫ

 

Ведущий рассылки:  Angel       © GoldFaq.ru Team     14 - ый выпуск



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу


В избранное