Использование прослушивателей событий в среде без графического интерфейса (DLL) (Delphi) - PullRequest
1 голос
/ 05 сентября 2011

Я пытаюсь преобразовать приложение с графическим интерфейсом, которое я создал в Delphi (на самом деле это Lazarus), в библиотеку (DLL).

В приложении с графическим интерфейсом пользователя я использовал OnDataChange прослушиватель событий, но я не могу понять, как сделать то же самое для библиотеки.

Вот как это выглядит в приложении с графическим интерфейсом:

procedure TForm1.Datasource2DataChange(Sender: TObject; Field: TField);
begin
  ZMakeRankedTable.Close;
  GetNN;
end;  

И в файле LFM устройства:

object Datasource2: TDatasource
DataSet = ZMakeRankedTable
OnDataChange = Datasource2DataChange
left = 184
top = 95
end       

Как мне сделать то же самое для библиотеки? Где я могу инициализировать прослушиватель событий?

Ответы [ 4 ]

3 голосов
/ 05 сентября 2011

Преобразуйте вашу форму в модуль DataModule и создайте экземпляр этого:

DTM := TMyDataModule.Create(nil);

Должно работать даже в приложениях без графического интерфейса.Я не использовал Lazarus для более чем нескольких тестов, но не вижу причин, почему это не сработает.

3 голосов
/ 05 сентября 2011

Что не так с созданием нового собственного класса, который будет делегатом вместо формы:

type
  TDataDelegate = class
  public
    procedure DataChange(Sender: TObject; Field: TField);
    etc...
  end;

procedure TDataDelegate.DataChange(Sender: TObject; Field: TField);
begin
  // Do what you normally would do in your form's event handler
end;

И просто обязательно создайте экземпляр класса

DataDelegate := TDataDelegate.Create;
DataSource2.OnDataChange := DataDelegate.DataChange;

и т.д ...

Другими словами, вместо формы используйте класс, который вы написали, для обработки событий различных классов. Как и в форме, каждая из процедур должна иметь подпись обработчика события. Разница лишь в том, что IDE не будет создавать эти методы для вас.

Вы также можете использовать модуль TDataModule, но я не уверен в последствиях. Преимущество будет поддержка IDE.

1 голос
/ 02 ноября 2014

Существует множество объяснений происходящих событий, но большинство из них либо неполные, либо непростые для понимания, либо слишком сжатые, либо не пошаговые, либо «не ОО» ... поэтому я решил дать описание моего подхода к тема.

Написание DLL означает инкапсуляцию. Я хотел бы предложить следующую структуру, которая использует интерфейсный класс. Конечно, это будет работать и без интерфейса, но разговоры о DLL подразумевают инкапсуляцию ... и интерфейсы являются основным инструментом / структурой для его достижения.
Иначе, (экземпляры) интерфейсов подсчитываются, то есть, если вы делаете это тщательно = всегда, код будет «вести себя лучше» (см. Другие записи об интерфейсах).
Я ссылаюсь на интерфейсы также по другой (хотя и связанной) причине - это не так уж и не по теме, как вы можете догадаться, возможно: он заставляет вас держать вещи отдельно = явно, как вы увидите. Тем не менее, у вас будет простой и легкий доступ ко всем «свойствам» реализующего объекта, конечно же и через границы DLL.

Для начала, красивый способ инкапсулировать содержимое в DLL - это экспортировать только одну процедуру, которая будет

export interfaceProvider;

, которая соответствует стандартной функции (не является частью класса)

function interfaceProvider() : IYourInterface;

В этой функции будет вызван конструктор класса! Глобальная переменная (внутри DLL) типа IYourInterface не обязательна, но упрощает жизнь.
Функция interfaceProvider () будет находиться в некоем обертке или шлюзе.
Среди других операционных методов интерфейс IYourInterface также будет демонстрировать метод

procedure assignDataChangeEvent( _event : TDataChangeEvent);

, который, в свою очередь, реализуется соответствующим классом, производным от интерфейса (конечно, также частью DLL), как и

TEncapsulatedStuffinDLL = class(Tinterfacedobject, IYourInterface)

Теперь просто имейте в виду, что события являются своего рода «элегантными обратными вызовами» или «элегантно организованными обратными вызовами». В Delphi ключ - это определенное определение типа. В качестве типа в том же модуле, в котором вы определяете интерфейс, и перед определением самого интерфейса добавьте что-то вроде этого

TDataChangeEvent = procedure(const Sender:TObject; const n : integer) of object;

Обратите внимание, что слушатель / получатель события (тот, кто находится вне / использует DLL) должен использовать точно такую ​​же сигнатуру параметров (см. Ниже: proc. DbChangeListener).
В классе, реализующем интерфейс, мы назвали его TEncapsulatedStuffinDLL, и вы сначала определите его как приватное поле

private
  OnDataChange : TDataChangeEvent ;
  ...

Далее нам понадобятся следующие два метода:

procedure TEncapsulatedStuffinDLL.assignDataChangeEvent( _eventListener : TDataChangeEvent ) ;
begin
          // here we assign the receiver of the callback = listener to the event
          OnDataChange := _eventListener ;
end;


procedure TEncapsulatedStuffinDLL.indicateChange;  
begin

         // release the event = perform the callback
            if Assigned(OnDataChange) then begin
              OnDataChange(self);
            end;
         // note that OnDataChange is pointing to the assigned receiver, since
         // the method assignDataChangeEvent has been called
end;

В разделе, где происходит соответствующий материал, мы называем релиз события

procedure TEncapsulatedStuffinDLL.someMethod();
begin

         // sth happening, then "releasing the event" = executing the callback
         // upon some condition we now do...
         indicateChange ;
end;

Последний бит - инициировать все это извне. Давайте предположим, что класс, в котором это происходит, называется TDllHost, поэтому мы сначала определим реальный метод слушателя

public
   procedure dbChangeListener(const Sender:TObject; const n : integer);

... реализовать это так

procedure TDllHost.dbChangeListener(const Sender:TObject; const n : integer);
begin
          .... doing sth based on the provided parameters
end;

и во время выполнения мы инициируем так (интерфейсы лучше всего определять в своем собственном модуле, конечно, несмотря на то, что Дели позволяет сделать это «запутанным» ... хотя это подтвердило бы всю идею инкапсуляции)

procedure TDllHost.init();
  var
        dbstuffInterface : IYourInterface ; // could also be global private to TDllHost
begin
        // please complete this (off topic) section about late binding a DLL
          ....

        // we would have a retrieval of the interface from the DLL
          dbstuffInterface := interfaceProvider();

        // and finally we provide the procedure pointer to the class  
          dbstuffInterface.assignDataChangeEvent( dbChangeListener );
        // the assignment of the method to the method variable 
        // is done by the class itself
end;

Существенным преимуществом использования интерфейсов для организации содержимого в DLL является то, что вы получаете намного лучшую поддержку со стороны IDE. Тем не менее, к сожалению, редко можно найти примеры программирования, в которых строго используются интерфейсы.
В случае, если вы не используете DLL из первых рук, процедура init () будет выглядеть иначе. Вместо загрузки DLL необходимо создать экземпляр dbstuffInterface посредством обычного вызова конструктора реализующего класса.

IMHO, обработка событий в DLL таким способом довольно проста и в целом применима с точки зрения ОО.Он должен работать на любом языке, поддерживающем интерфейсы (и процедурные типы).Это даже мой предпочтительный способ организации обратных вызовов / событий, если я вообще не использую DLL ... пока, в более поздний момент времени можно легко переключиться на полную инкапсуляцию с использованием DLL.Единственным (второстепенным) недостатком может быть то, что такая DLL, вероятно, не может быть использована в C-стандартах.Если вы хотите использовать его, скажем, в Java, потребуется дополнительная оболочка, чтобы вернуться к POP-NO (обычные старые процедуры, без объектов).

1 голос
/ 05 сентября 2011

На самом деле, это хорошо объяснено здесь :

Проблема в том, что указатель метода (OnDataChange) должен быть процедурой объекта (например, TForm),не обычная процедура.

...