rho*_*ody 9 delphi dll function
我正在尝试动态调用驻留在函数表中的过程或函数的能力.特定的应用程序是一个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;
Run Code Online (Sandbox Code Playgroud)
我检索指向此记录的指针,声明为:
apiPtr : PAPI;
Run Code Online (Sandbox Code Playgroud)
假设我还可以访问记录中每个条目的过程名称,参数数量和参数类型.
假设我想调用add函数.要添加的函数指针将是:
@apiPtr^.add // I assume this will give me a pointer to the add function
Run Code Online (Sandbox Code Playgroud)
我假设除了使用一些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;
Run Code Online (Sandbox Code Playgroud)
结束;
但我不能让它工作,它返回第一个参数的值而不是结果.也许我的调用约定错了,或者我误解了如何在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^);
Run Code Online (Sandbox Code Playgroud)
任何建议都感激不尽.我很欣赏有些人可能觉得这不应该是这样做的方式,但我仍然很好奇它是否至少是可能的.
更新1我可以确认添加功能正在获取正确的值来进行添加.
更新2如果我将添加的签名更改为:
add : function (var a, b, c : double) : double;
Run Code Online (Sandbox Code Playgroud)
并且我将结果分配给c里面的add,然后我可以在参数数组中检索正确的答案(假设我再添加一个元素,3而不是2).因此问题是我误解了函数返回值的方式.任何人都可以解释函数如何返回值以及如何最好地检索它们?
更新3我有我的答案.我应该猜到了.Delphi通过不同的寄存器返回不同的类型.例如,整数通过EAX返回,另一方面通过ST(0)返回.要将ST(0)复制到结果变量,我必须使用"FST结果"而不是"MOV p,EAX".我至少我现在知道原则上可以做到这一点.这是否是一件明智的事情是我现在必须考虑的另一件事.
这是一个XY问题:你想要做X,并且,无论出于何种原因,你已经确定Y是解决方案,但是你在制作Y时遇到了麻烦.在你的情况下,X是通过指针调用外部函数,Y是手动推送堆栈上的参数.但要完成X,你并不真正需要做ÿ.
表达式@apiPtr^.add不会为您提供指向该函数的指针.它会给你一个指向记录add字段的指针TAPI.(因为add是记录的第一个成员,该字段的地址将等于保存的地址apiPtr;在代码中Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer)).)该add字段包含指向该函数的指针,因此,如果这是您想要的,只需使用apiPtr^.add(并注意)这^在Delphi中是可选的).
要使用的最佳调用约定是stdcall.任何支持导出DLL函数的语言都将支持该调用约定.
您不需要汇编程序或任何其他棘手的堆栈操作来调用您的函数.您已经知道函数的类型,因为您使用它来声明add.要调用该字段指向的函数,只需使用与调用普通函数相同的语法:
z := apiPtr.add(x, y);
Run Code Online (Sandbox Code Playgroud)
编译器知道add字段的声明类型,因此它将为您安排堆栈.
这是一个很难解决的问题。在运行时动态访问 DLL 中的方法的一种方法是使用外部函数接口库,例如libffi、dyncall或DynaCall()。然而,这些都还没有被移植到 Delphi 环境中。
如果应用程序要将 DLL 中的一组方法与 DLL 提供的 Rtti 信息连接起来,并将它们暴露给脚本语言(例如 Python),一个选择是编写 Delphi 代码来检查 DLL 并写出ctypes兼容脚本它可以在运行时加载到嵌入式 Python 解释器中。只要事先定义 DLL 方法可以处理的一组有限但足够的类型,这就是一个实用的解决方案。