О загрузке дочерней формы (MDIChild) из DLL
Данную статью меня заставило написать огромное количество вопросов в Круглом Столе (а теперь еще и в Подводных Камнях) насчет размещения дочерней формы в библиотеке DLL. Честно говоря, у меня никогда не возникало такой необходимости и я обходился простым
приложением. Но масса вопросов без ответа посадило меня за кнопки вечерком. Говорят, что те ответы, которые, тем не менее, присутствуют, не работающие. И проверять мне их совсем не хотелось. Я решил начать решать проблему с нуля.
Поначалу я решил досконально разобраться в работе TForm и TApplication дабы точно представлять себе, как эти оболочки взаимодействуют с Windows, но потом понял, что ковыряться в сотнях строк исходников мне совсем неохота. Я просто посмотрел и увидел, что
кроме, собственно handl-ов эти компоненты оперируют со своими всяческими внутренними служебными списками (обычно TList) и передачей хэндла тут не обойдешься. Для работы форм необходимы оба глобальных (для программы) объекта: и TApplication и TScreen. Подг
ружаемая DLL, если использует разного рода формы и контролы их тоже создает. Но они другие! В смысле другие instances, которые и знать не знают о таких-же объектах в главном приложении. Но они есть и убивать их тоже не хочется (мало-ли чего случится, если
убить объект TApplication, даже в DLL). Поэтому решение пришло следующее. Создаем в нашей DLL две служебные функции, функцию вызова нашей дочерней формы и две переменные (пишу StdCall потому что всегда DLL-ки так оформляю, это удобно):
var
DLLApp: TApplication;
DLLScr: TScreen;
procedure InitPlugin(App, Scr: integer); StdCall;
begin
DLLScr := Screen;
Screen := TScreen(Scr);
DLLApp := Application;
Application := TApplication(App);
end;
procedure DonePlugin; StdCall;
begin
Screen := DLLScr;
Application := DLLApp;
end;
function CreateMDI: integer; StdCall;
begin
Result := integer(TfrmMyChildForm.Create(Application));
end;
exports
InitPlugin,
DonePlugin,
CreateMDI;
|
Итак, в начале программы я открываю библиотеку (LoadLibrary), пролучаю функции (GetProcAddress) и инициализируюплагин (InitPlugin(integer(Application), integer(Screen))), передавая ему ссылки на объекты Application и Screen и они сохраняются в переменных
внутри DLL. По окончании работы я его деинициализирую (DonePlugin), восстанавливая для dll-ки его объекты (для корректной деинициализации этих самых внутренних TApplication и TScreen), потом выгружаю библотеку (FreeLibrary). Функция создания дочернего окн
а возвращает объект формы (а по сути указатель) и с ним можно работать, либо используя переменную - родителя (TForm например) или абстрактный класс с которого наследуется форма в DLL (MyForm := TForm(CreateMDI)).
Неприятности такого похода состоят в том, что разные инструменты разработки (и даже разные версии одной и той же среды) могут быть (скорее всего, но я не проверял) несовместимы. Т.е. DLL с формой, изготовленной в одной версии Дельфи может не работать с пр
иложением, скомпилированным в другой. Ведь внутренние структуры объектов и таблицы методов могут не совпадать. Но это не слишком дорого. Есть еще пробляма дублирования кода VCL. Но что делать! Или так или пакеты. Вообще говоря в наше время гигабайтов и Wi
n2K спорить о сотне килобайт просто скучно.
Для дех, у кого не вышло: я написал и скомпилировал тестовый пример (Delphi 2). Он работает. Все возможности MDI сохраняются, вроде menu-merging, меню Window и Caption в главной форме. Тексты и скомпилированное приложение прилагаются.
Напоследок. Я не претендую на полное исследование темы. Возможно в других версиях Дельфи есть и другие необходимые глобальные обьекты. Возможно, если формы будут использовать печать, то необходимо передать таким-же образом и объект Printer, однако это сам
и можете проверить. Стоит посмотреть, чем занимается delphimm.dll, ведь она устраивает общий менеджер памяти, может и еще чего интересное делает. Удачи всем.
|