【问题标题】:It is safe to process a callback method between dll compiled with packages and dll compiled without them (Delphi)?在使用包编译的dll和没有它们编译的dll之间处理回调方法是否安全(Delphi)?
【发布时间】:2012-04-23 06:25:37
【问题描述】:

我使用的是 Delphi 2007,我有这个案例:

{ CommonUnit.pas }
type
  // there is a callback which I want to process
  TFooBar = procedure(Sender: IInterface) of object; stdcall;

  // there is an interface which is used by all modules
  IFoo = interface
  ['{0FAA4B2B-E82A-4A2A-B55F-C75EC53A1318}']
    procedure Bar(Callback: TFooBar); stdcall;
  end;

{ UnitInModuleCompiledWithoutPackages.pas }
type
  // there is a class which implements IFoo
  // and it's defined in Module One compiled without packages
  TFoo = class(TInterfacedObject, IFoo)
  public
    // implementation is ommited
    procedure Bar(Callback: TFooBar); stdcall;
  end;

{ UnitInModuleCompiledWithPackages.pas }
// there is a code in Module Two compiled with packages
type
  TSomeClass = class
  public
    // implementation is ommited
    procedure SomeMethod(Sender: IInterface); stdcall;
  end;

var
  SomeObject: TSomeClass; // assigned by somehow
  Foo: IFoo; // assigned by somehow

begin
  // ...
  Foo.Bar(SomeObject.SomeMethod); // so it is safe?
  // ...
end;

我知道当我尝试在Foo.Bar 中传递对象引用时,如果它是这样声明的,这将是内存损坏:

type
  IFoo = interface
  ['{0FAA4B2B-E82A-4A2A-B55F-C75EC53A1318}']
    // TSomeClass now declared in CommonUnit.pas
    procedure Bar(CallbackObject: TSomeClass); stdcall;
  end;

这是因为TSomeClass 在模块一中的实现与模块二中的不同(不同的内存管理器等等)。
但是方法引用呢?
我在 Embarcadero 的文档中没有发现任何可以解决这些问题的内容。

【问题讨论】:

    标签: delphi dll callback


    【解决方案1】:

    您的代码很好。当您传递方法指针 TFooBar 时,您传递了两个指针,一个函数指针和一个实例指针。当您调用该方法时,所有版本的 Delphi 都会执行完全相同的操作来调用该方法,因为调用约定强制执行精确的二进制接口。并且所有版本的 Delphi 都以相同的方式表示方法指针。

    您关心的问题有:

    1. 不同的内存管理器。这里没有问题,因为我们没有进行堆分配。
    2. 不同编译器上的不同对象表示。这里没有问题,因为调用方法指针不依赖于对象表示。它依赖于代码指针和数据指针(在模块之间通过值传递)和调用约定(约定一致)。

    【讨论】:

      【解决方案2】:

      正如 David Heffernan 已经回答的那样,使用方法指针很好。使用任何类型的“回调对象”而不是方法指针的唯一原因是:

      • IFoo 实例可能会在 Bar 返回后保留指针,在这种情况下,最好将回调描述为 事件,并且您应该传入一个 事件接收器 em> 接口,保证正确的生命周期管理。
      • IFoo.Bar 的实现可能会调用多种替代回调方法,在这种情况下,您还应该考虑传入单个接口(不是类实例,因为您自己声明)而不是多个方法指针。李>

      【讨论】:

      • +1 使用接口的另一个原因可能是您希望允许与非 Delphi 构建的模块互操作
      • @DavidHeffernan:是的,特别是在 64 位代码中,因为调用约定的特殊性。在 32 代码中,您始终可以将任何方法解释为带有额外 Self 参数的过程。在某些情况下,这在 64 位代码中不起作用,更准确地说,如果方法是返回任何需要隐式 Result 参数的函数(例如接口,或任何大于 64 位的堆栈分配类型)。
      • 我在 x64 上不遵循您的逻辑。确实,您无法在 x64 下使用堆栈玩同样简单的 thunking 游戏。但是,如果您只是将该方法声明为带有额外 Self 参数的普通旧函数,那么您就可以开始了。我想。
      • @DavidHeffernan:在 x86 上,只要两个编译器都支持寄存器调用约定,您可以通过添加 Self 参数轻松地将任何方法转换为函数。在 x64 上,如果方法需要隐式 Result 参数,则这不起作用,因为方法的隐式 Self 参数始终在 rcx 中传递,而隐式 Result 参数在 rdx 中传递给方法,但在 rcx 中传递给函数。你也不能通过用过程替换它来解决这个问题,因为结果指针应该在 rax 中返回。
      猜你喜欢
      • 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
      相关资源
      最近更新 更多