如何在Delphi中动态调用命名过程或函数

时间:2013-06-04 18:08:25

标签: delphi function dll

我正在尝试动态调用驻留在函数表中的过程或函数的能力。特定的应用程序是一个DLL,它导出一个指向函数表的指针以及有关参数和类型数量的信息。然后,宿主应用程序能够查询DLL并调用函数。如果它们是对象方法,我可以使用Rtti来调用它们,但它们是正常的过程和函数。 DLL必须导出普通函数指针而不是对象,因为DLL可以用任何语言编写,包括C,Delphi等。

例如,我在DLL中声明并填写了一条记录:

TAPI = record
        add  : function (var a, b : double) : double;
        mult : function (var a, b : double) : double;
end;
PAPI = ^TAPI;

我检索指向此记录的指针,声明为:

apiPtr : PAPI;

假设我还可以访问记录中每个条目的过程名称,参数数量和参数类型。

假设我想调用add函数。要添加的函数指针将是:

@apiPtr^.add  // I assume this  will give me a pointer to the add function

我假设除了使用一些asm在堆栈上推送必要的参数并检索结果之外没有别的办法吗?

第一个问题,将程序声明为cdecl的最佳调用约定是什么?在通话之前组装堆栈似乎最简单。

第二个问题,网上是否有任何实际可行的例子?我遇到了http://www.swissdelphicenter.ch/torry/showcode.php?id=1745(DynamicDllCall),它接近我想要的但我简化如下,现在它返回一个结果的指针(EAX):

function DynamicDllCall(proc : pointer; const Parameters: array of Pointer): pointer;
var x, n: Integer;
    p: Pointer;
begin
n := High(Parameters);
if n > -1 then begin
   x := n;
   repeat
     p := Parameters[x];
     asm
       PUSH p
     end;
     Dec(x);
   until x = -1;
end;
asm
  CALL proc
  MOV p, EAX  <- must be changed to "FST result" if return value is double
end;
result := p;

端;

但我无法使其工作,它会返回第一个参数的值而不是结果。也许我的调用约定错了,或者我误解了如何在EAX中检索结果。

我按如下方式调用DynamicDllCall:

var proc : pointer;
    parameters: array of Pointer;
    x, y, z : double;
    p : pointer;
begin
  x:= 2.3; y := 6.7;
  SetLength(parameters, 2);
  parameters[0] := @x;  parameters[1] := @y;
  proc := @apiPtr^.add;
  p := DynamicDllCall(proc, Parameters);
  z := double (p^);

感激地收到任何建议。我感谢有些人可能觉得这不应该是这样做的方式,但我仍然很好奇它是否至少是可能的。

更新1 我可以确认添加功能正在获取正确的值来进行添加。

更新2 如果我将添加的签名更改为:

add  : function (var a, b, c : double) : double;

我将结果分配给c里面的add,然后我可以在参数数组中检索正确的答案(假设我再添加一个元素,3而不是2)。因此问题是我误解了函数返回值的方式。任何人都可以解释函数如何返回值以及如何最好地检索它们?

更新3 我有答案。我应该猜到了。 Delphi通过不同的寄存器返回不同的类型。例如,整数通过EAX返回,另一方面通过ST(0)返回。要将ST(0)复制到结果变量,我必须使用&#34; FST结果&#34;而不是&#34; MOV p,EAX&#34;。我至少我现在知道原则上可以做到这一点。这是否是一件明智的事情是我现在必须考虑的另一件事。

2 个答案:

答案 0 :(得分:9)

这是一个XY问题:你想做 X ,并且,无论出于何种原因,你已经决定 Y 是解决方案,但你遇到了麻烦使 Y 工作。在您的情况下, X 通过指针调用外部函数 Y 手动推送堆栈上的参数。但是要完成 X ,你真的不需要做 Y

表达式@apiPtr^.add不会为您提供指向该函数的指针。它会为您提供指向add记录的TAPI字段的指针。 (由于add是记录的第一个成员,该字段的地址将等于apiPtr中的地址;代码中为Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer))。)add field 保存指向函数的指针,所以如果这是你想要的,只需使用apiPtr^.add(并注意^在Delphi中是可选的。)

要使用的最佳调用约定是stdcall。任何支持导出DLL函数的语言都将支持该调用约定。

您不需要汇编程序或任何其他棘手的堆栈操作来调用您的函数。您已经知道函数的类型,因为您使用它来声明add。要调用该字段指向的函数,只需使用与调用普通函数相同的语法:

z := apiPtr.add(x, y);

编译器知道add字段的声明类型,因此它将为您安排堆栈。

答案 1 :(得分:1)

这是一个难以解决的问题。在运行时动态访问DLL中的方法的一种方法是使用外部函数接口库,例如libffidyncallDynaCall()。然而,这些都没有被移植到Delphi环境中。

如果应用程序要将DLL中的一组方法与DLL提供的Rtti信息连接起来并将它们暴露给Python等脚本语言,那么可以选择编写检查DLL的Delphi代码并写出一个ctypes兼容脚本,可以在运行时加载到嵌入式Python解释器中。只要先定义DLL方法可以处理的有限但足够的类型集,这是一个实用的解决方案。