【问题标题】:Notifying about events from dll to main app通知从 dll 到主应用程序的事件
【发布时间】:2012-03-12 20:55:55
【问题描述】:

我正在开发打算跨平台的应用程序。我曾经使用 Windows 消息,但现在我放弃了它。我用回调替换了消息,但是无论我可以使用不同的技术,当不使用 Windows 消息时,我都不知道有不同的可能性。

我有主要的 exe 应用程序和一些 dll 插件。我在 dll 中有一些对象和线程,我想通知主应用程序 DLL 对数据结构所做的一些更改。

正如我所说,我目前正在处理一些回调。为了提供与不同语言(C++、VB、C#)的兼容性,我有非对象类型的回调。我不确定其他语言是否支持对象回调。

所以我的问题是:

  • Windows 消息的替代品(跨平台)有哪些?回调可以代替消息吗?
  • 其他语言是否支持对象回调?
  • 我猜其他语言有不同的技术来替代消息?

【问题讨论】:

    标签: delphi dll callback messages


    【解决方案1】:

    您当然可以使用回调函数来代替消息。您不能使用回调方法,因为只有 Delphi 和 C++ Builder 了解如何调用 Delphi 方法指针。但是,您可以将回调 objects 与任何支持 COM 的语言一起使用。这是一个插件通知应用程序数据结构已更改的示例:

    1. 定义一个接口。

      type
        IDataStructureChanged = interface
          ['{GUID}']
          procedure Call; stdcall;
        end;
      

      您可以向该方法添加一些参数,以便插件可以告诉数据结构如何更改,或者传递一些值来指示哪个插件发出通知。

    2. 在应用程序中实现它。

      type
        TDataStructureChangedListener = class(TInterfacedObject, IDataStructureChanged)
        private
          FForm: TForm;
          procedure Call; stdcall;
        public
          constructor Create(Form: TForm);
        end;
      

      当您实例化该类时,您可以将一个对程序主窗体的引用传递给它,或者当插件最终调用Call 方法时您的程序需要能够采取行动的任何其他信息。实现Call 以使您的应用程序在数据结构发生变化时执行它需要执行的任何操作。

    3. 在初始化每个插件时传递对它们的引用。

      ChangeListener := TDataStructureChangedListener.Create(Self);
      for i := 0 to Pred(PlugIns.Count) do
        PlugIns[i].Init(ChangeListener);
      

      插件应该存储一个监听器对象的引用,当数据结构发生变化时,它可以调用Call方法来通知你的应用程序。

    我在这里描述的是通常称为事件接收器的东西。您的程序中可以有多个。如果有多个事件要处理,您可以为每种事件设置一个单独的接口,或者您可以将它们全部分组到一个接口中,并为每个事件设置不同的方法。您可以为每个插件设置不同的接收器对象,也可以为每个插件提供对同一接收器对象的引用,然后传递插件 ID 参数。

    【讨论】:

      【解决方案2】:

      我肯定会使用回调。主应用可以给DLL一个回调函数在需要的时候调用,然后回调函数本身可以根据需要向应用发送窗口消息。

      【讨论】:

        【解决方案3】:

        我同意雷米的观点,(!)。一个简单的回调允许处理程序实现它选择的任何类型的进一步通信 - 它可以发布消息,它可以将参数推送到队列中,无论它想要什么。如果你想跨平台,你将不得不求助于传入和传出简单类型。通常在设置回调时传入“用户上下文”指针。回调将此指针传递给处理程序。这允许调用者将上下文对象作为指针/int 传递并在处理程序中恢复它(通过将指针/int 转换回对象)。然后,处理程序可以在上下文中调用方法,无论是 Delphi、C++ 等。

        【讨论】:

          【解决方案4】:

          所以我的问题是: Windows 消息的替代方案(跨平台)是什么?回调可以代替消息吗?

          是的,您可以用回调替换消息。

          其他语言支持对象回调吗?

          您不应该将对象方法用作回调。可移植代码的常见做法是使用句柄(通知调用约定):

          DLL 源代码:

          type
            THandle = LongWord;
            {$IF SizeOf(THandle) < SizeOf(Pointer))}
            {$MESSAGE Error 'Invallid handle type'}
            {$ENDIF}
          
            TCallback = procedure(const aHandle: THandle); cdecl;
          
            var
                gCallback: record
                  Routine: TCallback;
                  Obj: TObject;
                  Info: string
            end;
          
            function Object2Handle(const aObj: TObject): THandle;
            begin
              Result:= THandle(Pointer(aObj))
            end;
          
            function Handle2Object(const aHandle: THandle; out aObj: TObject): Boolean;
            begin
              if gCallback.Obj <> nil then
                if aHandle = Object2Handle(gCallback.Obj) then
                begin
                  aObj:= gCallback.Obj;
                  Result:= true;
                  Exit // WARRNING: program flow disorder
                end;
          
              aObj:= nil;
              Result:= false
            end;
          
          procedure DoCallback();
          begin
            if Assigned(gCallback.Routine) then
              gCallback.Routine(Object2Handle(gCallback.Obj))
          end;
          
          procedure SetupCallback(const aCallback: TCallback); cdecl;
          begin
            gCallback.Routine:= aCallback;
          end;
          
          procedure DoSomething(const aHandle: THandle; out aInfo: string); cdecl;
          var
            O: TObject;
          begin
            if Handle2Object(aHandle, O) then
              aInfo:= Format('%s class object %s', [O.ClassName(), gCallback.Info])
          end;
          
          procedure Test();
          begin
            gCallback.Obj:= TStream.Create();
            try
              gCallback.Info:= 'created';
              DoCallback();
            finally
              FreeAndNil(gCallback.Obj)
            end;
            gCallback.Obj:= TMemoryStream.Create();
            try
              gCallback.Info:= 'will be freed';
              DoCallback();
            finally
              FreeAndNil(gCallback.Obj)
            end
          end;
          
          exports
            SetupCallback,
            DoSomething,
            Test;
          

          可执行源:

          procedure Cb(const aHandle: THandle); cdecl;
          const
            STUPID: THandle = 1;
            EQUALLY_STUPID = $DEAD;
          var
            S: string;
          begin
            DoSomething(STUPID, S);
            DoSomething(aHandle, S);
            DoSomething(EQUALLY_STUPID, S)
          end;
          
          begin
            SetupCallback(@Cb);
            Test()
          end.
          

          已编辑:你现在不能在腿上开枪。

          我猜其他语言有不同的技术来替代消息?

          操作系统有一些消息选项。但是真正便携的并不多。

          你也可以使用:

          • 套接字,
          • (在这种情况下,IMO 太大了?)准备好的消息系统(我最喜欢的0MQ

          【讨论】:

          • 如果 DLL 知道 EXE 将在句柄参数中存储的东西的类型,然后 DLL 甚至 使用句柄参数作为该类型,那么你'已经错过了处理点。应该允许提供句柄的模块在其中存储 任何 数据类型,而不必担心句柄的消费者以任何方式使用它,除非将其传递回句柄提供者。在您的示例中,EXE 必须存储有效的 TObject 引用。如果没有,那么 DoSomething 将会崩溃。也可以直接传递 TObject 而不是这个 THandle charade。
          • 并传递 TObject 引用“提供与不同语言的兼容性”和可移植性?!?我只是展示了最简单的句柄实现,仅此而已。
          • 我的问题很简单。我不需要传递对象我只需要通知主应用程序一些更改,以便我可以刷新 GUI。我正在寻找跨平台且可与其他语言一起使用的解决方案。我想我会用回调来做。我只需要一些批准和我的问题的不同方面。谢谢。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多