在主机应用程序和DLL之间传递一个包含方法的记录

5

是否可以在不使用运行时包或共享内存DLL的情况下,在主机应用程序和DLL模块之间传递一个包含函数/过程的记录类型(Delphi 2006及以上版本)?

为了简单起见,我们假定我们的记录类型不包含任何字符串字段(因为这当然需要Sharemem DLL),以下是一个示例:

TMyRecord = record
  Field1: Integer;
  Field2: Double;
  function DoSomething(AValue1: Integer; AValue2: Double): Boolean;
end;

所以,简单地说:我能在不使用运行时包或共享内存DLL的情况下,在主机应用程序和DLL之间传递TMyRecord的“实例”(无论是哪个方向),并从主机EXE和DLL中执行DoSomething函数吗?

2
我怀疑你想要将数据和代码都传递给动态链接库(DLL)。正确的做法是使用接口来实现。你所接受的答案只传递数据,而没有传递代码。 - David Heffernan
David:DLL和EXE共享一个包含记录类型的公共单元......在Dorin完成的测试用例中,从主机可执行文件传递到DLL的记录允许DLL执行“DoSomething”方法,因此他的解决方案完全符合我的需求。通常我会同意接口是正确的选择,但由于这特别涉及我正在开发的“智能类型”,我需要能够在主机和DLL之间传递记录本身(现在我可以了)。谢谢 :) - LaKraven
1
只是让您知道,即使在 EXE 中创建记录,执行的方法也是 DLL 中的方法。 - David Heffernan
是的...知道这点很好,但在这种情况下,这是期望的行为 :) - LaKraven
2个回答

7
我不建议这样做,无论它是否有效。 如果你需要使DLL能够操作TMyRecord实例,最安全的选择是通过导出普通函数来实现,例如:

DLL:

type
  TMyRecord = record 
    Field1: Integer; 
    Field2: Double; 
  end; 

function DoSomething(var ARec: TMyRecord; AValue1: Integer; AValue2: Double): Boolean; stdcall;
begin
  ...
end;

exports
  DoSomething;

end.

应用:

type 
  TMyRecord = record  
    Field1: Integer;  
    Field2: Double;  
  end;  

function DoSomething(var ARec: TMyRecord; AValue1: Integer; AValue2: Double): Boolean; stdcall; external 'My.dll';

procedure DoSomethingInDll;
var
  Rec: TMyRecord;
  //...
begin 
  //...
  if DoSomething(Rec, 123, 123.45) then
  begin
    //...
  end else
  begin
    //...
  end;
  //...
end; 

1
+1 我也不建议这样做,因为在未来我们不知道它的支持情况如何,但问题是是否可能...另外,我不知道“记录方法”是如何处理的,但我认为这只是编译器的魔法,也就是说,当你调用“记录方法”时,它只是调用一个方法,在其中将记录作为第一个/最后一个参数传递,其余的是方法的参数...当然我可能错了。 - user497849
1
另一个缺点是需要相同的方法代码同时存在于DLL和EXE中。你不能在EXE中实现该方法并直接调用DLL,反之亦然。你需要一个VMT才能使其工作,而记录没有这个功能。如果代码发生变化,必须重新编译EXE和DLL以保持同步。你可以传递第二个记录,其中包含函数指针,当需要时将其传递给主记录。这将允许你集中代码,因此只有EXE或DLL才能直接访问方法并充当另一个模块的代理。 - Remy Lebeau
2
记录必须包含方法和类操作符才能按预期运行。请注意,您接受的答案不符合此标准。当您将记录发送到不同的模块时,您会发送数据但不会发送代码。 - David Heffernan
@marjan 是的,对齐必须匹配。打包是一种选择,但不一定是最好的选择。您只需要确保对齐匹配,并使用 $ALIGN 实现此效果。 - David Heffernan
2
如果dll和exe同步更新,那就没关系了。而且现在需求也不是很明确。这就是为什么我在你的回答中添加了澄清。个人建议使用接口。 - David Heffernan
显示剩余5条评论

4
如果我正确理解了您的问题,那么您可以这样做,这是一种方法:

testdll.dll

library TestDll;

uses
  SysUtils,
  Classes,
  uCommon in 'uCommon.pas';

{$R *.res}

procedure TakeMyFancyRecord(AMyFancyRecord: PMyFancyRecord); stdcall;
begin
  AMyFancyRecord^.DoSomething;
end;

exports
  TakeMyFancyRecord name 'TakeMyFancyRecord';

begin
end.

uCommon.pas <- 这是一个单元,应用程序和dll都会使用它,在其中定义了你的高级记录类型。

unit uCommon;

interface

type
  PMyFancyRecord = ^TMyFancyRecord;
  TMyFancyRecord = record
    Field1: Integer;
    Field2: Double;
    procedure DoSomething;
  end;

implementation

uses
  Dialogs;

{ TMyFancyRecord }

procedure TMyFancyRecord.DoSomething;
begin
  ShowMessageFmt( 'Field1: %d'#$D#$A'Field2: %f', [ Field1, Field2 ] );
end;

end.

最后是一个测试应用程序,文件 -> 新建 -> VCL窗体应用程序,在窗体上拖放一个按钮,将uCommon.pas包含在uses子句中,添加对外部方法的引用。

procedure TakeMyFancyRecord(AMyFancyRecord: PMyFancyRecord); stdcall;
  external 'testdll.dll' name 'TakeMyFancyRecord';

在按钮的点击事件中,添加以下内容:
procedure TForm1.Button1Click(Sender: TObject);
var
  LMyFancyRecord: TMyFancyRecord;
begin
  LMyFancyRecord.Field1 := 2012;
  LMyFancyRecord.Field2 := Pi;
  TakeMyFancyRecord( @LMyFancyRecord );
end;

免责声明:

  • 适用于D2010;
  • 在我的机器上编译通过!

享受吧!


David Heffernan的编辑

仅为了100%清晰,这个代码中执行的DoSomething方法是在DLL中定义的方法。在EXE中定义的DoSomething方法从未被执行。



1
谢谢你为我测试这个 :) +1 并标记为正确答案! - LaKraven
使用指向记录的指针对行为没有影响。您同样可以使用普通值参数、const 参数等。 - David Heffernan
@DavidHeffernan 是的,谢谢你,但是有很多种方法可以达到同样的目的!(: - user497849
1
我的观点是,你的回答暗示了使用指向记录的指针有些特殊之处。实际上并没有。 - David Heffernan
好的,是的,直到5分钟后才知道你也可以使用“const param”,编译器会自动处理。 - user497849
但是这是什么魔法?关于参数传递的信息传输方式并不重要。你的回答非常误导人。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接