Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > взаимодействие приложения и формы в dll


Автор: Teleport 7.12.2008, 16:58
В вопросе есть работа с бд, но сам вопрос сам по сути - о взаимодействии с dll. Потому в этом разделе.
Не так давно начал делать приложения с вызовом dll, в которой есть форма. Возникла проблема. 

У меня в главном приложении - есть DBGrid, в котором отображаются данные из бд baza.mdb. На главной форме есть кнопка Вызов - которая отображает форму из dll.
Также на главной форме есть еще кнопки 2: удалить и  добавить. Они добавляют или удаляют записи в бд.

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

Прикрепляю сделанные проекты.
Mainn.dpr - проект главного приложения
libr.dpr - проект dll
Unit1.pas- модуль формы dll

Примечание: ADOConnection.Caonnected и ADOTale1.Active в проекте в инспекторе объектов поставлены в значение false. Они активизируются при событии form1.Activate.

Автор: THandle 7.12.2008, 17:13
Teleport, ну... возможно глупая затея, но:

Может быть сделать 2 сообщения, одно на удаление записи, а другое на добавление.
И соответственно слать друг дружке такие сообщения и выполнять соответствующие действия?

Автор: Akella 7.12.2008, 17:15
В библиотеке тоже создай подключение (ADOConnection и т.д.) и при вызове передавай из вызывающего приложения в библиотеку параметры подключения.

Добавлено через 1 минуту и 54 секунды
Т.е. работай, как будто из двух разных приложений.

Автор: Teleport 7.12.2008, 17:37
2 THandle - наверное, не глупая затея. Подробней мне нужно объяснить на моем примере?... smile
2 Akella - в библиотеке все создано! сразу создал. И что только из этого?? обновления-то не происходит одновременно...
Как эти параметры подключения передать?

Автор: THandle 7.12.2008, 18:37
Teleport, покажу просто пример. Можно вообще использовать любой тип оповещения в принципе-то.

Пример с сообщениями в аттаче.

Автор: Teleport 7.12.2008, 19:03
2 THandle - А вот в этих строках что такое 1001 и 1003?
Код

  WM_EXU_DELETE = WM_USER + 1001;
  WM_EXU_ADD = WM_USER + 1003;


Автор: THandle 7.12.2008, 19:53
Teleport, цифры smile Разве не видно? smile 

Это номера сообщений.

http://msdn.microsoft.com/en-us/library/ms644927(VS.85).aspx

Цитата

Application-Defined Messages

An application can create messages to be used by its own windows or to communicate with windows in other processes. If an application creates its own messages, the window procedure that receives them must interpret the messages and provide appropriate processing.

Message-identifier values are used as follows:

    * The system reserves message-identifier values in the range 0x0000 through 0x03FF (the value of WM_USER – 1) for system-defined messages. Applications cannot use these values for private messages.
    * Values in the range 0x0400 (the value of WM_USER) through 0x7FFF are available for message identifiers for private window classes.
    * If your application is marked version 4.0, you can use message-identifier values in the range 0x8000 (WM_APP) through 0xBFFF for private messages.
    * The system returns a message identifier in the range 0xC000 through 0xFFFF when an application calls the RegisterWindowMessage function to register a message. The message identifier returned by this function is guaranteed to be unique throughout the system. Use of this function prevents conflicts that can arise if other applications use the same message identifier for different purposes. 



Автор: Akella 7.12.2008, 20:07
THandle, да ему полностью нужно учить сообщения винды и работа с dll, что толку, что мы напишем ему сейчас примеры, он же всё равно не поймёт в них ничего.

Добавлено через 3 минуты и 50 секунд
Цитата(Teleport @  7.12.2008,  17:37 Найти цитируемый пост)
Как эти параметры подключения передать?

Как, как? А как ты что-то передаёшь в dll, так же и строку подключения передай!

Автор: Teleport 7.12.2008, 20:17
тему считаю закрытой

Автор: Akella 7.12.2008, 20:19
Код

procedure TfmMain.LoadPlugins();
Var
  i, iDllHandle:integer;
  PlugExec        : procedure(sender :TObject) of object;
  PlugType        : function : integer;  //Объявление функции, которая будет возвращать тип плугина
  GetConnectInfo  : procedure(fbclient,DB:PChar;db_connected:boolean);
  GetUserData     : procedure(UID:integer;UserName:PChar);
  MenuCaption : function : PChar;
  act: TAction;
  NewItem :TMenuItem;
begin
  iDllHandleAreas  := 0;
  slPlagins        := TStringList.Create;
  slPlaginsHandles := TStringList.Create;
  FindPlugins(IncludeTrailingPathDelimiter(sAppPath)+'Plugins\');
  if slPlagins.Count <= 0 then begin
    FreeAndNil(slPlagins);
    exit;
  end;
  LOG(self.Name+' Загрузка плагинов');
  //если найдены плагины, то пытаемся их загрузить
  for I := 0 to slPlagins.Count - 1 do begin
    //iDllHandleAreas
    iDllHandle := LoadLibrary(PChar(slPlagins[i]));
    slPlaginsHandles.Add(IntToStr(iDllHandle));

    if iDllHandle <> 0 then begin
      @plugExec := GetProcAddress(iDllHandle,'PluginExec');
      @plugType := GetProcAddress(iDllHandle,'PluginType');
      @GetConnectInfo := GetProcAddress(iDllHandle,'GetConnectInfo');
      @GetUserData    := GetProcAddress(iDllHandle,'GetUserData');

      GetUserData(UserData.UserID,PChar(UserData.UserName));
      GetConnectInfo(PChar(DM.fibDB.LibraryName),PChar(DM.fibDB.DBName),DM.fibDB.TestConnected);

      NewItem         := TMenuItem.Create(Self);
      @MenuCaption    := GetProcAddress(iDllHandle,'MenuCaption');
      act             := TAction.Create(am1);
      act.ActionList  := am1;
      act.onExecute   := plugExec;
      act.caption     := MenuCaption;
      if MenuCaption = 'Участки [RA Innovations]' then
        iDllHandleAreas := iDllHandle;

      NewItem.Action  := act;
      mmMainMenu.Items.Items[PlugType].Add(NewItem);
      Log(self.Name+' Плагин успешно загружен: '+MenuCaption);

    end;
  end;//for
end;



Код

library Areas;

uses
  ShareMem,
  SysUtils,
  uAreas in 'uAreas.pas' {fmAreas},
  uAddPhone in 'uAddPhone.pas' {fmAddPhone},
  uCSIDL in '..\..\uCSIDL.pas',
  uPhoto in 'uPhoto.pas' {fmPhoto1},
  uOptionsAreas in 'uOptionsAreas.pas' {fmOptionsAreas};

{$R *.res}

type
  TUSerData = record
    UserName, Password:String;
    UserID:integer;
  end;

type
  TConnectInfo = record
    fbclient,DB  : PChar;
    db_conn : boolean;
  end;

Var
// kvx_handle:integer;
  UserData   : TUserData;
  ConnectInfo : TConnectInfo;


function MenuCaption:Pchar;
begin
  result := 'Участки [RA Innovations]';
end;

function PluginType:integer;
begin //5 - Индекс главного меню
  result := 5;//Участки
end;


procedure PluginExec(Sender:Tobject);
begin
  if Assigned(fmAreas) then FreeAndNil(fmAreas);
  fmAreas := TfmAreas.Create(nil);
  fmAreas.UserData.UserName := UserData.UserName;
  fmAreas.UserData.Password := UserData.Password;
  fmAreas.UserData.UserID   := UserData.UserID;

  with fmAreas do begin
    fibDbAreas.ConnectParams.UserName  := 'SYSDBA';
    fibDbAreas.ConnectParams.Password  := 'masterkey';
    fibDbAreas.LibraryName             := ConnectInfo.fbclient;
    fibDbAreas.DatabaseName            := ConnectInfo.DB;
    fibDbAreas.Connected               := ConnectInfo.db_conn;
  end;
  fmAreas.Show;

end;

procedure GetUserData(UID:integer;UserName:PChar);
begin
  UserData.UserName := UserName;
  UserData.UserID   := UID;
end;


procedure GetConnectInfo(fbclient,DB:PChar; db_connected:boolean);
begin
  ConnectInfo.fbclient := fbclient;
  ConnectInfo.DB       := DB;
  ConnectInfo.db_conn  := db_connected;
end;

procedure ShowEditForm(ID:Integer);
begin
  bDoInsert   := false;
  iEditingID  := ID;
  LoadData(ID);
  fmAreas.Show;

end;


exports
  PluginType, MenuCaption, PluginExec, GetUserData, GetConnectInfo, ShowEditForm;


begin
end.

Автор: THandle 7.12.2008, 20:24
Цитата(Teleport @  7.12.2008,  20:17 Найти цитируемый пост)
тему считаю закрытой 


Проблему то решил? smile

Akella, что это? smile  Я лично сходу не врубился во все это дело? Откуда-то вынутый кусок кода?

Автор: Akella 7.12.2008, 20:39
думал, что по названиям догадаетесь. smile 
Код

//Это поиск и загрузка dll`ок
procedure TfmMain.LoadPlugins();
...



Код

это код одного из плагинов
library Areas;
....





Из вызывающего приложения порядок вызова процедур плагина
Код

  @plugExec := GetProcAddress(iDllHandle,'PluginExec');
      @plugType := GetProcAddress(iDllHandle,'PluginType');
      @GetConnectInfo := GetProcAddress(iDllHandle,'GetConnectInfo');
      @GetUserData    := GetProcAddress(iDllHandle,'GetUserData');
      GetUserData(UserData.UserID,PChar(UserData.UserName));
      GetConnectInfo(PChar(DM.fibDB.LibraryName),PChar(DM.fibDB.DBName),DM.fibDB.TestConnected);


Каждый плагин создаёт в главном меню вызывающего приложения свой новый пункт меню. Через этот пункт меню можно вызвать главную форму плагина


Создаём новый пункт меню:
Код

      NewItem         := TMenuItem.Create(Self);
      @MenuCaption    := GetProcAddress(iDllHandle,'MenuCaption');
      act             := TAction.Create(am1);
      act.ActionList  := am1;
      act.onExecute   := plugExec;//цепляем процедуру вызова главной формы плагина
      act.caption     := MenuCaption;
      NewItem.Action  := act;


{
куда цеплять, к какому пункту меню указано в самом плагине
function PluginType:integer;
begin //5 - Индекс главного меню
  result := 5;//Меню "Участки" вызывающего приложения
end;

}

      mmMainMenu.Items.Items[PlugType].Add(NewItem);

Автор: Teleport 7.12.2008, 21:35
2 THandle - не решил и решу не скоро. smile Буду с нуля в ней разбираться. Пусть через полгода решу - но решу. smile
Akella прав, мне эти примеры ни о чем просто. smile я знаю немного  работу с dll по статьям из delphi world и все. Открыл статьи по процессам и потокам. Пытаюсь работать с сообщениями. 

Автор: Akella 8.12.2008, 09:43
Читай, как работать с плагинами. Я по-моему взял из DRKB, точно не помню.

Автор: lukas 10.12.2008, 20:41
Могу еще посоветовать экспортировать функции из *.exe , т.е. использовать exe как dll, для того чтобы функции нормально экспортировались из exe, их нужно поместить в *.dpr файл. Ну и вызывать нужные тебе функции в *.exe из dll.

Автор: Teleport 12.12.2008, 23:40
2 lukas - вот смотри, я тут немного освоил экспорт функций из dll. Вобщем если в моем основном приложении вызвать dll форму и нажать в этой форме Button3 - то изменение переменной произойдет одновременно и в dll и в основном приложении! Только вот никак не могу додуматься - как передать обновления DBGrid? Я думаю, что нужно передавать ADOTable1.Close и ADOTable1.Open - только вот как в моем примере их передать?

Автор: Rrader 13.12.2008, 10:34
lukas, прошу, не стоит давать таких советов, грязнее метода и нет наверное smile 

Teleport, посмотрел аттач и ужаснулся. Брутфорс, и то с грубыми ошибками... Тебе же THandle предложил по сообщениям обновление организовать, это приемлемый и удобный метод. Чем не устраивает? Хотя THandle обучен еще одному эффективному методу решения подобных задач, придет, ответит smile 

Автор: Teleport 13.12.2008, 12:36
2 Rrader - подробнее насчет ошибок smile разве плохо так переменную передавать? Просто скажи почему - чтобы я понял smile
Жду ответа. smile

Автор: Rrader 13.12.2008, 13:10
Код

procedure Izm(ADOTable1: TADOTable);

Передача объекта - небезопасна.

Код

 function ex_as: string;
begin
 result:=a;
end;


Передача строки String (наверняка у тебя это AnsiString) - еще более небезопасная затея. Темы избитые, статьи можно читать разные - http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1206 и http://sources.ru/magazine/0804/delphistr.html 


Автор: Teleport 13.12.2008, 14:43
читаю про строки...  smile 

Автор: Teleport 14.12.2008, 14:43
я немного разобрался с примером, который мне дал Thandle. И присоединил его к своей программе с бд. Все работает, но не совсем так как надо - обновления в DBGrid-ах запаздывают. Т.е. далеко не с первого клика происходят обновления. Прошу рассмотреть мой проект и сказать, как исправить чтобы обновления были с первого клика по кнопке. 

Mainn.dpr - проект главного приложения
libr.dpr - проект dll
Unit1.pas- модуль формы dll

Автор: Teleport 16.12.2008, 22:30
проблема актуальна. Сообщения, как я понял, проходят сразу, а вот DBGrid - не обновляется сразу. Запаздыает обновления до следующего нажатия кнопки. :( в чем проблема не пойму... Какой совет может дадите? исходнике в посте выше....  :( :(

Автор: THandle 17.12.2008, 15:29
Цитата(Rrader @  13.12.2008,  10:34 Найти цитируемый пост)
Хотя THandle обучен еще одному эффективному методу решения подобных задач, придет, ответит 


 smile 

Вот примерчик связи с помощью интерфейсов(в аттаче).

Обновляется не сразу думаю потому, что это дело связано уже с базами данных) То есть все выполняется, Close|Open делается, но обновления не происходит.
Teleport, тут уже копать в свойства базиальных компонентов...

Автор: Teleport 20.12.2008, 20:23
2 THandle - спасибо за пример, скачал, разбираюсь. Работает. Обновляется не сразу, причем я думаю, что это дело не в самих бд... Просто вот смотри - когда с сообщениями работали - твой пример тоже, то там ведь ShowMessage - тоже невсегда с первого раза выскакивали? Все же думаю, что тут проблема в технологии передачи... Не утверждаю, конечно, но думаю, что именно так.
А за интерфейсы отдельное спасибо - пытаюсь разобраться. Про с пример с сообщениями почти все понятно. Спасибо. Пытаюсь, пока безуспешно, найти решение для обновления моментального...

Автор: THandle 20.12.2008, 20:28
Teleport, а ты во время отладки библиотеки посмотри - каждый ли раз срабатывает нужное действие. То есть отлаживай саму библиотеку.

Автор: Teleport 20.12.2008, 20:59
хм...  smile да, вот незадачу я себе нашел  smile 
Как я увижу-то каждый или некаждый раз?  smile 

Автор: THandle 20.12.2008, 21:27
Teleport, открываешь проект библиотеки.

В Project Options, Dubugger, ставишь в Host Application нашу программу (mainn.exe или как там её).
Дальше на строки:

Код

  ExuDllForm.ADOTable1.Close;
  ExuDllForm.ADOTable1.Open;


(это в методе TExuDllInterface.Release)

ставишь брякпойнты.

Дальше запускаешь все это дело(Run or F9)
Жмешь "Вызов".

А дальше жмешь в окне программы кнопки удалить/добавить и смотришь, заходит ли в код, на котором стоят бряки.

Автор: Teleport 23.12.2008, 22:38
спасибо, пробую))

Автор: lukas 24.12.2008, 08:51
Teleport, ну правильно, если ты один и тот же используешь unit и в длл и в exe, это вообще небезопасно, и не известно к каким результатам это приведет.

Rrader, не знаю я пользуюсь, просто не нужно злоупотреблять таким способом. Сообщения вообще встают в очередь, а тут напрямую выполнение функции из exe. Главное знать где находиться exe, а загрузить функцию из exe можно также как и длл. Вообщем у меня еще проблем не возникало. Если могут возникнуть какие -нибудь проблемы, напишите тогда о них... мне самому интересно будет.

Автор: Rrader 24.12.2008, 16:07
lukas, для этого придуманы интерфейсы. Затрудняюсь назвать хотя бы одно преимущество в связи EXE и DLL через экспорт из EXE по сравнению с интерфейсами. Ненужное наследство от PE-файлов

Автор: lukas 25.12.2008, 12:56
Rrader, а разве интерфейсы не работают в разы медленнее...?

Автор: Rrader 25.12.2008, 13:16
lukas, хотелось бы услышать обоснование smile 

Автор: lukas 25.12.2008, 13:44
Rrader, я конечно профессионально не смогу объяснить, но если мне не изменяет память, то перед вызовом метода интерефейса происходит куча операций, т.к. программа даже не знает куда она обращается, а при вызове функции из dll или же из exe мы один раз находим адрес функции и используем ее в дальнейшем.

Автор: Alexeis 25.12.2008, 14:34
Цитата(lukas @  25.12.2008,  12:44 Найти цитируемый пост)
Rrader, я конечно профессионально не смогу объяснить, но если мне не изменяет память, то перед вызовом метода интерефейса происходит куча операций, т.к. программа даже не знает куда она обращается, а при вызове функции из dll или же из exe мы один раз находим адрес функции и используем ее в дальнейшем. 

  Функция интерфейса вызывается также как обычная виртуальная функция, т.е. просто косвенный вызов. Фактически 2е операции 1я получение адреса по фиксированному смещению в таблице VMT интерфейса и переход по этому адресу.

Автор: Rrader 25.12.2008, 14:49
Alexeis, +1

Посмотрим, что же происходит.... Пишем библитоеку следующего содержания:
Код

library Client;

uses Windows, Dialogs;

type
  IFace = interface
    procedure DoIt;
  end;

  TFace = class(TInterfacedObject, IFace)
    procedure DoIt;
  end;

procedure ExportFunc();
begin
  ShowMessage('Message');
end;

function IntfFunc: IFace;
begin
  try
    Result := TFace.Create;
  except
    Result := nil;
  end;
end;

{ TFace }

procedure TFace.DoIt;
begin
  ShowMessage('Message');
end;

exports
  IntfFunc,
  ExportFunc;

begin
end.

Здесь используются два вида связи - через экспорт и через интерфейс. Теперь пишем приложение, которое будет их вызывать.
Код

unit UMain;

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;

  IFace = interface
    procedure DoIt;
  end;

var
  Form1: TForm1;

function IntfFunc: IFace; external 'Client.dll';
procedure ExportFunc; external 'Client.dll';

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  P: IFace;
begin
  P := IntfFunc;
  { Коды }
  ExportFunc;
  P.DoIt;
  { Вставлено для наглядности, чтобы не мешать предыдущий вызов с очисткой }
  P.DoIt;
end;

end.


Прямой вызов ExportFunc:
Код

    call    ExportFunc
ExportFunc:
    jmp dword ptr [Address] ; На ShowMessage

Вызов DoIt:
Код

    mov eax, [ebp-4]
    mov edx, [eax]
    call dword ptr [edx + 0Ch]
...
edx + 0Ch:
    add eax, -0Ch
    jmp DoIt; На ShowMessage

Автор: lukas 25.12.2008, 15:36
Alexeis, вопрос, этот адрес вычисляется каждый раз при вызове или как то кешируется (запоминается)?

Я просто может что-то не понимаю, но как вычисляется адрес в dll функции, он вычисляется по названию 1 раз, т.е. мы нашли адрес (допустим через GetProcAddress() ), а далее при вызове мы его уже не ищем.

Автор: Alexeis 25.12.2008, 15:50
lukas, вычисляется на лету, в этом и суть динамического связывания. Тебе что жалко одной операции сложения?  smile . Вызов функции по адресу происходит в несколько раз дольше чем, получение ее адреса, потому такой мелочью можно пренебречь почти всегда. 

Автор: Teleport 27.12.2008, 15:16
Вывод, вобщем, один - передача через интерфейсы. Правильно понял?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)