gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-03 14:43:41 27 4
gpt4 key购买 nike

我正在尝试动态调用驻留在函数表中的过程或函数的能力。特定的应用程序是一个 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 返回,而 double 则通过 ST(0) 返回。要将 ST(0) 复制到结果变量,我必须使用“FST 结果”而不是“MOV p, EAX”。我至少现在知道原则上可以做到这一点。这是否明智是我现在必须考虑的另一件事。

最佳答案

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

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

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

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

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

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

关于delphi - 如何在 Delphi 中动态调用命名过程或函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16924633/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com