Изменение компонентов формы вызывающего приложения из Dll

Вопросы программирования и использования среды Lazarus.

Модератор: Модераторы

Изменение компонентов формы вызывающего приложения из Dll

Сообщение btsoft » 04.04.2014 06:54:47

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

Код: Выделить всё
Procedure FormSizeWidth(var f:TForm); stdcall; export;
var
  i:integer;
Begin
  f.Width:=f.Width + 50;
  for i := 0  to f.ComponentCount - 1 do
    begin
      if (f.Components[i].Name = 'btnTest') then
          begin
            (f.Components[i] as TButton).Caption := 'Привет из Dll!';
            //f.Components[i].Name := 'NameFromDll';
          end;
    end;
End;   

Здесь я просто пробовал что-нибудь изменить в переданной форме: изменить надпись на определенной кнопке и изменить ширину формы. В итоге:
ширина формы меняется, но вот эта строчка:
Код: Выделить всё
(f.Components[i] as TButton).Caption := 'Привет из Dll!';

вызывает ошибку Invalid type cast. Press OK to ignore and risk data corruption.
При этом в самом приложении аналогичный код работает без проблем.
Если указанную строку закомментировать и расскоментировать эту:
Код: Выделить всё
f.Components[i].Name := 'NameFromDll';

то никаких ошибок нет, и имя компонента реально меняется! Что это значит и как это преодолеть?
btsoft
незнакомец
 
Сообщения: 7
Зарегистрирован: 04.04.2014 06:24:48

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение btsoft » 06.04.2014 10:58:56

Ап!
P.S. У меня вопрос такой сложный, что ответить довольно сложно? Или такой идиотский, что отвечать даже как-то стыдно и не о чем, типа "почему 2*2 равно 4"?
btsoft
незнакомец
 
Сообщения: 7
Зарегистрирован: 04.04.2014 06:24:48

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение hinst » 06.04.2014 12:11:55

я бы по-другому сделал

Добавлено спустя 1 минуту 4 секунды:
Re: Изменение компонентов формы вызывающего приложения из Dll
потому, что так скорее всего работать не будет, или будет не правильно работать
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение SSerge » 06.04.2014 12:30:27

btsoft писал(а):ширина формы меняется, но вот эта строчка:

Код: Выделить всё
(f.Components[i] as TButton).Caption := 'Привет из Dll!';


вызывает ошибку Invalid type cast. Press OK to ignore and risk data corruption.


Нельзя из/в dll транслировать объекты, управляемые встроенным менеджером памяти компилятора: объекты, строки, динамические массивы.
Потому что у dll свой менеджер кучи, у основной программы - свой. Объекты, соответственно, не синхронизированы. У вас скорее всего получается ссылка на временную строковую переменную, которую пытается удалить м.п. основной программы, а адрес этому м.п. не принадлежит. Вот посему общайтесь с dll через PChar и поля простых типов. Строки - запрещено. Всякие "array of byte;" - запрещено. Даже если опыт программирования в delphi говорит что можно. Либо заставляйте dll работать под управлением менеджера памяти основной программы.
SSerge
энтузиаст
 
Сообщения: 971
Зарегистрирован: 12.01.2012 05:34:14
Откуда: Барнаул

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение btsoft » 06.04.2014 17:17:24

Спасибо за ответы. Жаль, получается, с помощью dll сложно работать с тем, что мне нужно.

hinst

А как по другому?
btsoft
незнакомец
 
Сообщения: 7
Зарегистрирован: 04.04.2014 06:24:48

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение hinst » 06.04.2014 21:26:34

Первая сложность это менеджер памяти, а вторая - то, что даже если будет общий менеджер памяти, то LCL не будет общий, поэтому скорее всего работать не будет, я лично сам не пробовал, не знаю. По-другому это значит что-нибудь придумать или изобрести своё, я в общем не пробовал, поэтому подробности сообщить не могу

Добавлено спустя 2 минуты 48 секунд:
Re: Изменение компонентов формы вызывающего приложения из Dll
Что я думаю, так это что
Это нужно для того, чтобы была некая базовая версия приложения, а уже для конкретного клиента можно было быстро и безболезненно переделать интерфейс и поведение.

Что можно скомпилировать для конкретного клиента приложение, а не раздавать разные .dll-файлы. Ведь какая разница, будет это один исполняемый файл + один из многих dynlib-файл или просто один из многих исполняемый файл

Добавлено спустя 32 секунды:
Re: Изменение компонентов формы вызывающего приложения из Dll
это на мой взгляд самое нормальное решение
Аватара пользователя
hinst
энтузиаст
 
Сообщения: 781
Зарегистрирован: 12.04.2008 18:32:38

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение скалогрыз » 07.04.2014 02:55:57

btsoft писал(а):Спасибо за ответы. Жаль, получается, с помощью dll сложно работать с тем, что мне нужно.
hinst
А как по другому?

1. pascalscript
2. lua
3. конфигурационные файлы

Добавлено спустя 2 часа 22 минуты 57 секунд:
SSerge писал(а):Всякие "array of byte;" - запрещено.

нужно заметить, что речь ведётся о динамических массивах, но не об открытых массивах.

итого:
Такой код использовать рисково (т.к. нет осбой гарантии, что массив не попытается перераспределится)
Код: Выделить всё
type
  TByteArray = array of byte;

procedure UseByteArray(var b: TByteArray);


а такой безопсно, т.к. ни счётчиков, ни перераспределения памяти тут нет. А значит и в dll-ке можно использовать.
Код: Выделить всё
procedure UseArrayOfBytes(var b: array of byte);

в Си он будет выглядеть как
Код: Выделить всё
void UseArrayOfByte(uint8* b, int count);
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение SSerge » 07.04.2014 12:31:29

скалогрыз писал(а):в Си он будет выглядеть как

Код: Выделить всё
void UseArrayOfByte(uint8* b, int count);




imho, не уверен, что var b:array of byte; в Си будет выглядеть именно так. Как логический аналог - да. Но как внутреннее представление... Собственно, поскольку в Си нет совпадающего типа данных, то можно говорить лишь о том, что передается указатель на первый элемент массива и где-то-там, в скрытых параметрах, зависящих от реализации компилятора, - длина массива
SSerge
энтузиаст
 
Сообщения: 971
Зарегистрирован: 12.01.2012 05:34:14
Откуда: Барнаул

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение stanilar » 07.04.2014 14:50:02

То, что заработал код изменяющий ширину, меня удивляет больше чем ошибка "Invalid type cast". Жаль нет под рукой лазаря.

Мой взгляд на ситуацию:
TButton из DLL и TButton из exe - два разных класса. Т.е. их реализация процедур/функций лежат в разных учатках памяти - для DLL в одном, а для EXE - в другом. Соответственно когда идет вызов (f.Components[i] as TButton).Caption, то обращение должно произойти к методу, реализованному в DLL, и то что возникает ошибка "Invalid type cast" - хвала компилятору, ибо это действительно так.
Лучше понять это поможет запись без оператора as: TButton(f.Components[i]).Caption. Очевидно, что реализация TButton.Caption различна для DLL и EXE.

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

Что делать?

1) Использовать интерфейсы (Советую не использовать из соображений крайне сложной поддержки и сопровождения, да и насколько я помню, FPC не поддерживает импорт интерфейсов из коробки).
2) Использовать пакеты (когда их запилят в FPC, а пока можно прототип и на D7 пописать).
3) Использовать прцедуры/функции в параметрах которых отсутствуют классы, т.е. перенести часть функций из DLL в EXE.
4) Еще раз подумать об архитектуре приложения, есть мнение что процедурный подход сильнее объктного.

З.Ы. Если уж очень сильно хочется реализовать работу между DLL и EXE через объекты, то можно попробовать запилить собственный аналог работы с handle, как это сделано для объектов WinAPI в VCL или LCL.
stanilar
постоялец
 
Сообщения: 289
Зарегистрирован: 09.03.2010 19:09:02

Re: Изменение компонентов формы вызывающего приложения из Dl

Сообщение скалогрыз » 07.04.2014 16:09:03

SSerge писал(а):imho, не уверен, что var b:array of byte; в Си будет выглядеть именно так. Как логический аналог - да. Но как внутреннее представление... Собственно, поскольку в Си нет совпадающего типа данных, то можно говорить лишь о том, что передается указатель на первый элемент массива и где-то-там, в скрытых параметрах, зависящих от реализации компилятора, - длина массива


правда, как обычно, в ассемблере

Код: Выделить всё
procedure Test(var a: array of byte); // здесь есть скрытый параметр размера
// иначе length(a) был бы невозможен
var
  i : integer;
begin
  for i:=0 to length(a)-1 do
    write(a[i],' ');
end;

var
  a : array of byte;
  b : array [0..6] of byte;
begin
  SetLength(a, 12); FillChar(a[0], length(a), $FF);
  Test(a);
  Test(b);
end.


а (MS)асм такой:
Код: Выделить всё
                ; так передаётся динамический массив
      mov   eax,dword ptr [U_P$OPENARR_A] 
      call   fpc_dynarray_high                       ; вызов процедуры для получения размера динамического массива
      mov   edx,eax                                      ; кладём длинну (результат функции) вторым параметров
      mov   eax,dword ptr [U_P$OPENARR_A] ; указатель на данные массива первым параметром
      call   P$OPENARR_TEST$array_of_BYTE ; вызваем процедуру

                ; так передаётся статический массив
      mov   eax,offset U_P$OPENARR_B      ; указатель на данные массива первым параметром
      mov   edx,6                                      ; длина (константа - статический же)  вторым параметром
      call   P$OPENARR_TEST$array_of_BYTE ; вызваем процедуру


Полистав документацию, я нигде не нашёл подвтерждения, что такой порядок будет всегда гарантирован (и не появятся ещё какие-нибудь параметры).
Зато очевидно, почему индексация открытых массивы всегда начинаются с 0 (как бы, кто не просил) и почему это ...сравнительно... безопасно для Си.
скалогрыз
долгожитель
 
Сообщения: 1803
Зарегистрирован: 03.09.2008 02:36:48


Вернуться в Lazarus

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 218

Рейтинг@Mail.ru
cron