На этом шаге мы рассмотрим особенности использования модальных форм при динамическом подключении DLL.
Если загружать DLL динамически, возникает ряд проблем. Первая из них заключается в том, что, как правило, загрузка и выгрузка библиотеки, из которой вызывается диалоговое окно, осуществляется в пределах одной функции:
HLib := LoadLibrary('FirstLib.dll'): if HLib <> 0 then begin ExecDialog:=GetProcAddress(HLib, 'ExecDialog'); if Assigned(ExecDialog) then begin if ExecDialog(Application.Handle, P) then {...}; end else ShowMessage('Метод "ExecDialog" не найден'); FreeLibrary(HLib); end else ShowMessage('Не найдена библиотека "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 - для этого указанный выше оператор в DLL выполнять не надо. Однако при этом на панели задач появляются две кнопки. Корректное решение проблемы заключается в присвоении нулевого значения свойству Application.Handle в 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], SizeOf(C), 0); if length(FDialog.OpenPictureDialog1.FileName) > 0 then StrPCopy(C,FDialog.OpenPictureDialog1.FileName); 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;
Ниже приведен код приложения, динамически вызывающего данную библиотеку:
type TExecDialog = function(AppHandle: THandle; var PictName: PChar): Boolean; stdcall; procedure TForm1.Button1Click(Sender: TObject); var P: PChar; S: String; ExecDialog: TExecDialog; HLib: THandle; begin HLib := 0; try HLib := LoadLibrary('FirstLib.dll'); if HLib <> 0 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('Метод "ExecDialog" не найден'); end else ShowMessage('Не найдена библиотека "FirstLib.dll"'); finally Application.ProcessMessages; if HLib <> 0 then FreeLibrary(HLib); end; end;
Отображение модальных форм редко осуществляется в COM DLL - обычно в COM DLL помещают расчетные методы. Но можно показывать и модальные формы - при этом не возникают проблемы со вторым экземпляром объекта TApplication. В COM DLL также следует динамически вызывать конструктор модальной формы.
На следующем шаге мы рассмотрим немодальные формы в DLL.