我正在试验动态调用驻留在函数表中的过程或函数的能力。特定的应用程序是一个 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我可以确认 add 函数正在获取正确的值来进行加法。
更新 2如果我将 add 的签名更改为:
add : function (var a, b, c : double) : double;
然后我将结果分配给 add 中的 c,然后我可以在参数数组中检索正确的答案(假设我再添加一个元素,3 而不是 2)。因此,问题是我误解了函数是如何返回值的。谁能解释函数如何返回值以及如何最好地检索它们?
更新 3我有我的答案。我应该猜到了。Delphi 通过不同的寄存器返回不同的类型。例如,整数通过 EAX 返回,而双精度则通过 ST(0) 返回。要将 ST(0) 复制到结果变量,我必须使用“FST 结果”而不是“MOV p,EAX”。我至少现在知道原则上可以这样做。这样做是否明智是我现在必须考虑的另一件事。