9
votes

I am experimenting with the ability to dynamically invoke procedures or functions that reside in a function table. The specific application is a DLL that exports a pointer to a function table together with information on the number of arguments and types. The host application then has the ability to interrogate the DLL and call the functions. If they were object methods I could use Rtti to invoke them but they are normal procedures and functions. The DLL has to export normal function pointers not objects because the DLL could be written in any language including C, Delphi etc.

For example, I have a record declared and filled out in a DLL:

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

I retrieve the pointer to this record, declared as:

apiPtr : PAPI;

Assume I also have access to the names of the procedures, number of arguments and argument types for each entry in the record.

Assume I want to call the add function. The function pointer to add will be:

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

I assume there is no other way other than to use some asm to push the necessary arguments on the stack and retrieve the result?

First question, what is the best calling convention to declare the procedure as, cdecl? Seems easiest for assembling the stack before the call.

Second question, are there any examples online that actually do this? I came across http://www.swissdelphicenter.ch/torry/showcode.php?id=1745 (DynamicDllCall) which is close to what I want but I simplified as below, it now returns a pointer (EAX) to the result:

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;

end;

but I can't get it to work, it returns a value for the first parameters instead of the result. Maybe I have the calling convention wrong or maybe I misunderstand how to retrieve the result in EAX.

I call DynamicDllCall as follows:

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^);

Any advice gratefully received. I appreciate that some may feel this isn't the way one should go about doing this but I am still curious if it is at least possible.

Update 1 I can confirm that the add function is getting the correct values to do the addition.

Update 2 If I change the signature of add to:

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

and I assign the result to c inside add, then I can retrieve the correct answer in the parameters array (assuming I add one more element to it, 3 instead of 2). The problem therefore is that I misunderstand how values are returned from functions. Can anyone explain how functions return values and how best to retrieve them?

Update 3 I have my answer. I should have guessed. Delphi returns different types via different registers. eg integers return via EAX, double on the other hand returns via ST(0). To copy ST(0) to the result variable I must use "FST result" rather than "MOV p, EAX". I least I now know it is possible in principle to do this. Whether it is a sensible thing to do is another matter I must now think about.

2
There used to be a nice introduction to Delphi ASM at www.delphi3000.com (articles/article_3766.asp), but that entire site is gone now...Andreas Rejbrand
Perhaps the documentation on Assembly Procedures and Functions would help. See the Function Results topic.Ken White
There isn't much out there and the official docs are not terrible useful. However I pieced together enough information from: stackoverflow.com/questions/15786404/fld-instruction-x64-bit and guidogybels.eu/docs/Using%20Assembler%20in%20Delphi.pdfrhody
Why not use dynamic typing in Delphi (duck typing). While I consider it gross, it's less gross than direct ASM manipulations as you're doing, which I consider very fragile and gross, not to mention preventing you from porting to 64 bit delphi. stackoverflow.com/questions/9497708/duck-typing-in-delphi-2007/…Warren P
@rhody. Unless I misunderstood something, what you are trying to do is what COM does, because of "the DLL could be written in any language" and COM offers binary interoperability.Jack G.

2 Answers

9
votes

This is an XY problem: You want to do X, and, for whatever reason, you've decided Y is the solution, but you're having trouble making Y work. In your case, X is call external functions via pointers and Y is manually push parameters on the stack. But to accomplish X, you don't really need to do Y.

The expression @apiPtr^.add will not give you a pointer to the function. It will give you a pointer to the add field of the TAPI record. (Since add is the first member of the record, the address of that field will be equal to the address held in apiPtr; in code, Assert(CompareMem(@apiPtr, @apiPtr^.add, SizeOf(Pointer)).) The add field holds a pointer to the function, so if that's what you want, just use apiPtr^.add (and note that the ^ is optional in Delphi).

The best calling convention to use is stdcall. Any language that supports exporting DLL functions will support that calling convention.

You don't need assembler or any other tricky stack manipulation to call your functions. You already know the function's type because you used it to declare add. To call the function pointed to by that field, simply use the same syntax as for calling an ordinary function:

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

The compiler knows the declared type of the add field, so it will arrange the stack for you.

1
votes

This is a hard problem to solve. One way to dynamically access methods in a DLL at runtime would be to use a foreign function interface library such as libffi, dyncall or DynaCall(). None of these however have yet been ported to the Delphi environment.

If the application is to interface a set of methods in a DLL together with Rtti information provided by the DLL and expose them to a scripting language such as Python, one option is to write Delphi code that inspects the DLL and writes out a ctypes compatible script which can be loaded into an embedded Python interpreter at runtime. So long as one defines before hand a limited but sufficient set of types that the DLL methods can handle, this is a practical solution.