在非gui环境(DLL)中使用事件侦听器(Delphi)

时间:2011-09-05 13:52:12

标签: delphi event-handling freepascal lazarus

我正在尝试将我在Delphi中创建的GUI应用程序(实际上是它的Lazarus)转换为库(DLL)。

在GUI应用程序中,我使用了OnDataChange事件监听器,但我似乎无法弄清楚如何为库做同样的事情。

以下是GUI App中的内容:

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 个答案:

答案 0 :(得分:3)

将表单转换为DataModule并创建一个实例:

DTM := TMyDataModule.Create(nil);

甚至可以在非GUI应用程序中工作。我没有使用Lazarus进行过多次测试,但我认为没有理由说这不起作用。

答案 1 :(得分:3)

创建一个属于委托的新类而不是表单有什么问题:

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支持。

答案 2 :(得分:1)

事实上,here已经很好地解释了这一点:

  

问题是方法指针(OnDataChange)需要是一个过程   一个对象(如TForm),而不是常规程序。

答案 3 :(得分:1)

关于周围的事件有很多解释,但大部分都是不完整的,或者不容易理解,压缩太多,或者不是一步一步,或者“不是OO”...所以我决定提供我的方法的描述话题。

编写DLL意味着封装。我想提出以下结构,它使用一个接口类。当然它也可以在没有接口的情况下工作,但是谈论DLL意味着封装......接口是实现它的核心工具/结构。
否则,(实例)接口被引用计数,这意味着,如果你彻底地=总是这样做,代码将“表现得更好”(参见关于接口的其他条目)。
我还提到接口是为了进一步(虽然是相关的)原因 - 它可能不像你猜测的那样偏离主题:它强制你将事物分开=显式,正如你将看到的那样。不过,您可以轻松,简单地访问实现对象的所有“属性”,当然也可以跨越DLL边界。

首先,在DLL中封装内容的一种漂亮的方法是只导出一个过程,这将是

export interfaceProvider;

对应于标准函数(不属于类)

function interfaceProvider() : IYourInterface;

在这个函数中,将调用类构造函数! IYourInterface类型的全局变量(在DLL内部)不是必需的,但可以简化生活 函数interfaceProvider()将位于一种包装或网关单元中 在其他操作方法中,接口IYourInterface也将展示方法

procedure assignDataChangeEvent( _event : TDataChangeEvent);

反过来是由从接口派生的相应类(当然也是DLL的一部分)实现的,如此

TEncapsulatedStuffinDLL = class(Tinterfacedobject, IYourInterface)

现在请记住,事件是一种“优雅的回调”,或“优雅组织的回调”。在Delphi中,关键是特定的类型定义。 作为定义接口的同一单元中的类型,在接口本身定义之前,添加类似于此的

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

请注意,事件的侦听器/接收器(在DLL外部/使用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。

恕我直言,以这种方式将事件处理到DLL中非常简单,并且从OO角度来看通常是适用的。它应该适用于支持接口(和过程类型)的任何语言。如果我根本不使用DLL,它甚至是我组织回调/事件的首选方式......但是,在稍后的时间点,可以使用DLL轻松切换到完整的封装。唯一(次要)缺点可能是这样的DLL可能无法通过C标准使用。如果你想在Java中使用它,那么为了回到POP-NO(普通的旧程序,没有对象),还需要一个进一步的包装器。