Форум: "Основная";
Текущий архив: 2005.08.21;
Скачать: [xml.tar.bz2];
ВнизПрорисовка формы в DLL Найти похожие ветки
← →
vers © (2005-07-29 14:27) [0]DLL динамически подключается к приложению, в DLL переменной Application присваивается Application приложения, создаются 2 формы, в качестве ParentWindow форм указывается хэндл формы приложения. Проблема: не прорисовываются некоторые компоненты форм DLL, например, SpeedButton"ы, при проведении через них мышью. То есть кнопка становится выпуклой при наведении на нее мышью, а в обратное состояние не возвращается, при уходе мышью с нее. В случае со SpeedButton"ами проблему решил путем установки таймера c интервалом в 100 мс, в обработчике для всех кнопок вызываю UpdateTracking, но как быть с компонентами, у которых нет метода UpdateTracking? Вызов Update или посылка сообщения WM_PAINT не помогают.
← →
vers © (2005-07-30 17:40) [1]Поясню вопрос примером:
приложение:
procedure TForm1.Button1Click(Sender: TObject);
begin
dllhandle:=LoadLibrary("c:\temp\dllforms.dll");
createform:=GetProcAddress(dllhandle,"createform");
CreateForm;
end;
dll:
procedure createform;
var
DLLForm : TDLLForm;
begin
DLLForm := TDLLForm.Create(Application);
DLLForm.Show;
// DLLForm.Free;
end;
На форме DLLForm находится пара SpeedButton"ов и XPManifest. Если провести по кнопкам мышью, они становятся выпуклыми, а в обратное состояние не возвращаются.
Также заметил, что если вместо DLLForm.Show использовать DLLForm.ShowModal и соответственно раскомментарить DLLForm.Free, то эта проблема не возникает. Но нужно использовать немодальную форму.
Переприсваивание объекта Application и отправка сообщений WM_PAINT в DLLForm проблему не решают. Помогает только периодический вызов SpeedButton.UpdateTracking, но этот метод есть не у всех компонентов.
← →
Юрий Зотов © (2005-07-30 17:47) [2]> Переприсваивание объекта Application
Еще и Screen переприсвоить не помешает. А вообще - код бы...
← →
Leonid Troyanovsky © (2005-07-30 17:56) [3]
> vers © (29.07.05 14:27)
> DLL динамически подключается к приложению, в DLL переменной
> Application присваивается Application приложения, создаются
> 2 формы, в качестве ParentWindow форм указывается хэндл
> формы приложения. Проблема: не прорисовываются
Пользуй package instead dll and be happy.
--
Regards, LVT.
← →
vers © (2005-07-30 18:40) [4]
> Юрий Зотов ©
Код:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, 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;
dllhandle:THandle;
createform:procedure(app:TApplication; scr:TScreen);
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
dllhandle:=LoadLibrary("c:\temp\project2.dll");
createform:=GetProcAddress(dllhandle,"createform");
CreateForm(Application,Screen);
end;
end.
--------------------------------
library Project2;
uses
SysUtils,
Classes,
Forms,
Unit2 in "Unit2.pas" {Form2};
var
Form : TForm2;
{$R *.res}
procedure createform(app:TApplication; scr:TScreen);
begin
Application:=app;
Screen:=scr;
Form := TForm2.Create(Application);
Form.Show;
end;
exports
createform;
begin
end.
> Leonid Troyanovsky ©
А можно поподробней про package? А то я про такое вообще не слышал...
← →
Leonid Troyanovsky © (2005-07-30 19:14) [5]
> > Leonid Troyanovsky ©
>
> А можно поподробней про package? А то я про такое вообще
> не слышал...
http://softwarer.ru/packages.html
Но, начинать надо с Delphi help.
--
Regards, LVT.
← →
Юрий Зотов © (2005-07-30 19:54) [6]> vers © (30.07.05 18:40) [4]
Вроде как, должно работать (правда, непонятно, зачем переприсваивать каждый раз и почему не восстанавливаются "родные" объекты перед выгрузкой DLL).
А вообще, радикальное решение - в [3]. DLL - это все же библиотека функций, а не объектов и экспорт форм из нее - моветон (хотя и можно, но от проблем не гарантирует). Поэтому лучше использовать BPL вместо DLL, либо уж хотя бы компилировать и приложение, и DLL с run-time пакетами.
← →
isasa © (2005-07-30 20:28) [7]Не совсем понятна мысль
procedure createform(app:TApplication; scr:TScreen);
begin
Application:=app;
Screen:=scr;
Form := TForm2.Create(Application);
...
а так не достаточноForm := TForm2.Create(app);
Application и Screen глобальные переменные для ВАП, в DLL переменной Application нет и по определению быть
не может т.к. у DLL нет своего адресного пространства.
← →
isasa © (2005-07-30 20:46) [8]Если я правильно понимаю идею, беспокоит вопрос о создании
фори при подключении,и их уничтожении при выгрузке библиотеки?
Не лучше ли поступить естественным путем, используя
DLL_PROCESS_ATTACH: locDLLForm := TDLLForm.Create(nil);
DLL_PROCESS_DETACH: locDLLForm.Free;
а експортировать указатель функцией
function GetForm: TDLLForm;
begin
result:=locDLLForm;
end;
← →
Юрий Зотов © (2005-07-30 21:16) [9]> isasa © (30.07.05 20:28) [7]
Вообще-то, Application и Screen - это глобальные объекты модуля Forms, а никакого не ВАП. И если этот модуль в DLL используется - значит, они в DLL присутствуют. Причем именно свои собственные, а не те же самые, что в загружающей программе.
> isasa © (30.07.05 20:46) [8]
Как быть, если требуется создавать и уничтожать форму много раз по ходу программы? Или если требуется произвольное количество экземпляров формы?
← →
Defunct © (2005-07-30 21:43) [10]Почему-то мне кажется что проблема не в экспорте. Даже не присваивая Application := app и не присваивая screen форма созданная в dll должна прекрасно работать. Может быть все дело в том, что вы присваиваете целый экземпляр Application, а не его Handle. При этом цикл выборки экземпляра Application в DLL продолжает работать со старым handle"ом.
Короче говоря насколько я помню, делал так:
1. Вообще не трогал Application.
2. Присваивал handle"у экземпляра Application в DLL - handle Application"а хост приложения.
Попробуйте так:procedure createform(AppHandle : Dword);
begin
Application.Handle := AppHandle;
Form := TForm2.Create(Application);
Form.Show;
end;
....
Createform:=GetProcAddress(dllhandle,"createform");
CreateForm(Application.Handle);
....
← →
Eraser © (2005-07-30 21:46) [11]Defunct © (30.07.05 21:43) [10]
Вряд ли... хэндл Application - это всего навсего дескриптор невидимого окна и не более того.
← →
Defunct © (2005-07-30 21:49) [12]Eraser © (30.07.05 21:46) [11]
Правильно, и он нужен не более чем для НЕ создавания дополнительного окна на панели задач. Я же говорю, что не трогая Application в DLL формы будут прорисовываться так, как положено.
← →
Defunct © (2005-07-30 21:55) [13]> Eraser
Вот отрыл в рабочем проекте:procedure UploadFileThroughISP( ACallingProcessHandle : DWord;
FileName: ShortString );stdcall;
begin
if ACallingProcessHandle <> 0 then
Application.Handle := ACallingProcessHandle;
with TISPProgrammerForm.Create( nil ) do
begin
fFileName := FileName;
L_FName.Caption := ExtractFileName( FileName );
ShowModal;
end
end;
← →
Юрий Зотов © (2005-07-30 22:23) [14]> Defunct
В справке так и рекомендуется. Но, как показывает практика, дочерние окна в DLL (например) при таком способе не работают, а при замене всего Application (и Screen) - работают. Аналогично и с другими глобальными объектами (скажем, если не подменять Session, то в DLL потребуется переоткрытие DataSet"ов).
Handle - это, в общем-то, всего лишь свойство Application. Естественно, подменяя Application, мы подменяем весь объект вместе со всеми его свойствами. Поэтому предположение о том, что "цикл выборки экземпляра Application в DLL продолжает работать со старым handle"ом" выглядит спорным.
← →
Defunct © (2005-07-30 22:37) [15]> Поэтому предположение о том, что "цикл выборки экземпляра Application в DLL продолжает работать со старым handle"ом" выглядит спорным.
Спорным, но вполне допустимым.
Мне почему-то кажется, что цикл выборки подмененного Application"а никуда не девается и как минимум при такой подмене мешает нам работать, мы же наблюдаем лишь следствие этого - неправильную прорисовку (как будто часть сообщений не обрабатывается). Почему бы при такой подмене не убивать внутренние (dll) экземпляры Application и Screen?
← →
Defunct © (2005-07-30 22:45) [16]> Юрий Зотов © (30.07.05 22:23) [14]
Со всем уважением к Вам, я губоко сомневаюсь, что Вы хотя бы раз применили в своих программах подобный код:
...
Application := app;
Screen := scr;
...
ведь он, как минимум, чреват несовместимостью dll и хост приложения при компиляции оных в разных версиях Delphi.
← →
Юрий Зотов © (2005-07-30 22:47) [17]> Defunct © (30.07.05 22:37) [15]
> цикл выборки подмененного Application"а никуда не девается
Естественно. Он просто начинает работать с другим Self. Соответственно, и с другим Handle.
> Почему бы при такой подмене не убивать внутренние (dll)
> экземпляры Application и Screen?
Потому что их надо будет восстановить перед выгрузкой DLL, чтобы она правильно финализировалась.
← →
Юрий Зотов © (2005-07-30 22:52) [18]> Defunct © (30.07.05 22:45) [16]
> я губоко сомневаюсь, что Вы хотя бы раз применили в своих
> программах подобный код:
Точно, не применял. И замену хэндла тоже не применял. Я вообще не применяю потенциально опасных вещей, чтобы потом не иметь с ними проблем. Тем не менее, в одном из проектов формы в DLL использовались ОЧЕНЬ активно (можно даже сказать, что на этом была построена идеология проекта). Просто все компилировалось с run-time пакетами - а потому все работало и без всяких трюков.
← →
Defunct © (2005-07-30 22:56) [19]Юрий Зотов © (30.07.05 22:47) [17]
> Естественно. Он просто начинает работать с другим Self. Соответственно, и с другим Handle.
хм.. т.е. методы остаются теже, что имеются в TApplication DLL подменяются только поля и свойства. Верная несовместимость на будущее ;>
← →
vers © (2005-07-31 00:40) [20]Подмена Application, Application.Handle и Screen не решали проблему. Она решилась только после того, как я поставил галочку "build with runtime packages" в приложении и в dll. Специально перенес программу на другой компьютер, на котором не установлена Delphi, для проверки (при этом пришлось еще захватить rtl и vcl пакеты).
При этом возникает другая проблема: разрабатываемые dll - plugin"ы для программы, подключаемые пользователем по мере надобности, возможно даже во время работы приложения. При этом (как раньше было) один plugin - одна dll. Сами файлы dll передаются по сети, используется свой протокол передачи. Перед подключением проверяется есть ли уже эта dll у пользователя и если есть, то импортируются несколько функций для определения имени и версии plugin"а, если они не совпадают, dll обновляется, иначе используется локально сохраненная dll. Но разные dll могут использовать одни и те же пакеты, следовательно нужно будет к каждому plugin"у приписать, какие пакеты он использует и проверять есть ли уже эти пакеты у пользователя. Но это уже дело техники.
Возник вопрос: а можно ли указать в программе путь, откуда будет загружаться пакет, или этот путь обязательно прописывать в переменную окружения?
← →
Юрий Зотов © (2005-07-31 01:04) [21]> vers © (31.07.05 00:40) [20]
Загружаемые пакеты ищутся по обычным правилам. Последовательность поиска есть в описании LoadLibrary.
← →
Юрий Зотов © (2005-07-31 01:08) [22]> vers © (31.07.05 00:40) [20]
Кроме того, пакет можно загрузить и самому (LoadPackage) - тогда можно явно указать и путь к нему.
← →
vers © (2005-07-31 02:04) [23]Вот как раз об этом-то и хотел спросить: какая последовательность действий при динамической загрузке пакетов?
Поставить галочку "build with runtime packages", стереть оттуда все пакеты из списка и уже в программе перед Application.Initialize вписать LoadPackage?
← →
Юрий Зотов © (2005-07-31 02:29) [24]> vers © (31.07.05 02:04) [23]
Зачем стирать из списка пакеты, которые программе нужны постоянно, без которых она просто не сможет работать? Например, пакеты VCL.
Не нужно их стирать, нужно просто поставить галочку. Компилятор сам все впишет и получится статическая загрузка этих пакетов - это как статическая линковка DLL. Что и нормально, раз эти пакеты все равно нужны всегда.
А динамически есть смысл загружать те пакеты, которые программе нужны не все время (загрузил - поработал - выгрузил). Либо пакеты с плагинами (какой надо - такой и загрузил). Функции таких пакетов вызываются по обычной схеме (GetProcAddress).
← →
vers © (2005-07-31 03:08) [25]А если вредный пользователь сотрет vcl, то программа вылетит при загрузке с ошибкой и не скачает уже никогда этот пакет...
А где экспортируются функции в этих пакетах? В основном файле пакета, как в dll, нельзя. И как можно импортировать из них классы? Видимо через GetClass? И вообще, где можно почитать о практической реализации пакетов? А то пока что все, что находил - это "что такое пакеты и для чего они нужны", а не "как самому написать пакет".
← →
Юрий Зотов © (2005-07-31 03:20) [26]> vers © (31.07.05 03:08) [25]
> А если вредный пользователь сотрет vcl, то программа вылетит
> при загрузке с ошибкой и не скачает уже никогда этот пакет
А если вредный пользователь сотрет DLL, то будет точно то же самое.
> А где экспортируются функции в этих пакетах?
> И как можно импортировать из них классы?
Как обычно, все просто объявляется в секциях interface модулей, которые входят в пакет. При статической загрузке пакета Ваша программа имеет полное право ссылаться на его модули в своих uses, при динамической используйте GetProcAddress.
> как самому написать пакет.
File - New - Package. Остальное как обычно - создаем в пакете юниты, в них пишем все, что нужно.
← →
vers © (2005-07-31 04:00) [27]
> Юрий Зотов © (31.07.05 03:20) [26]
> А если вредный пользователь сотрет DLL, то будет точно то
> же самое.
Не совсем. Если пользователь стирает DLL, основная программа запускается, видит, что файла DLL не хватает, и скачивает его снова. DLL тут не необходимая для запуска программы часть. Но это не принципально: сотрет пользователь пакет - сам дурак, пусть снова качает полный дистрибутив.
> при динамической используйте GetProcAddress
А классы из пакета при динамической загрузке можно достать? Оччень заманчиво было бы использовать классы напрямую, чем писать для них функции-обертки.
← →
Юрий Зотов © (2005-07-31 04:58) [28]> vers © (31.07.05 04:00) [27]
> Оччень заманчиво было бы использовать классы напрямую, чем
> писать для них функции-обертки.
Очень хороший вариант - использование интерфейсов. Пишете юнит (или несколько юнитов), где декларируете набор нужных интерфейсов. Эти юниты через uses используются как программой, так и пакетом. В пакете сидят классы, реализующие эти интерфейсы. Еще пакет экспортирует функции с заранее известными именами, а эти функции возвращают ссылки на интерфейсы. Программа вызывает такую функцию, получает от нее ссылку на интерфейс - а потом напрямую использует свойства и методы этого интерфейса. Очень просто и очень удобно получается.
← →
vers © (2005-07-31 06:54) [29]А пример я нашел здесь:
http://www.delphikingdom.com/mastering/plugins.htm
← →
Юрий Зотов © (2005-07-31 07:12) [30]И все же использование интерфейсов представляется более предпочтительным.
← →
vers © (2005-07-31 09:41) [31]Я так и делаю. Одно плохо: если на форму, которая в пакете, положить еще один компонент, класс формы поменяется, и придется перекомпилировать не только пакет, но и приложение, вследствие чего исключается возможность "горячей" замены плагинов. А остальные неизменные классы я уже описал в интерфейсных модулях.
← →
isasa © (2005-07-31 12:21) [32]>И все же использование интерфейсов представляется более предпочтительным.
Очень правильная мысль.
>Вообще-то, Application и Screen - это глобальные объекты модуля Forms
В DLL будут существовать Application и Screen, еслиuses ..., Forms,..
но будут ли они инициализированы. Аналог theApp из Visual Studio, там это более прозрачно.
← →
Юрий Зотов © (2005-07-31 14:57) [33]> isasa © (31.07.05 12:21) [32]
> В DLL будут существовать Application и Screen, если
> uses ..., Forms,..
Сабж называется "Прорисовка формы в DLL". Чтобы получить форму в DLL, в ней придется написать uses ..., Forms,..
> но будут ли они инициализированы.
Будут.
← →
vers © (2005-07-31 16:43) [34]А можно ли узнать, какие пакеты требуются для пакета, до его загрузки? И можно ли как-то перехватить вывод сообщения об ошибке "приложению не удалось запуститься, поскольку такой-то пакет не был найден", возникающее при загрузке пакета, когда один из требуемых им пакетов отсутствует? try .. except не помогает, видимо потому, что сообщение генерирует Windows. Порывшись в исходниках LoadPackage обнаружил, что она вызывает SafeLoadLibrary, которая в свою очередь вызывает LoadLibrary, может можно заменить вызов LoadPackage на LoadLibrary? При этом ошибочное состояние сигнализируется handle=0, а не сообщением об ошибке.
← →
vers © (2005-07-31 18:13) [35]
> vers © (31.07.05 16:43) [34]
> может можно заменить вызов LoadPackage на LoadLibrary?
точнее на
SafeLoadLibrary("c:\package.bpl",SEM_NOOPENFILEERRORBOX+SEM_FAILCRITICALERRORS)
← →
Юрий Зотов © (2005-08-01 04:02) [36]> vers © (31.07.05 16:43) [34]
> А можно ли узнать, какие пакеты требуются для пакета, до его
> загрузки?
Можно. У разработчика пакета. Сам пакет об этом сообщить не сможет, раз он еще не загружен. А других вариантов, вроде как, и нет.
> ошибочное состояние сигнализируется handle=0, а не сообщением
> об ошибке.
А что же еще надо? Проверяйте хэндл, если надо - возбуждайте ошибку.
← →
vers © (2005-08-01 16:32) [37]Я про то, что если использовать LoadPackage, то сообщения об ошибке при отсутствии необходимого пакета никак не избежать. Вместо этого я вызываю SafeLoadLibrary, а затем InitializePackage.
Список требуемых пакетов можно узнать до его загрузки. Для этого нужно загрузить его PE-заголовок и просмотреть таблицу импорта. Файлы с расширением *.bpl в нем - требуемые пакеты. Но для моего проекта это уже чересчур.
Страницы: 1 вся ветка
Форум: "Основная";
Текущий архив: 2005.08.21;
Скачать: [xml.tar.bz2];
Память: 0.57 MB
Время: 0.037 c