Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically invoke a named procedure or function in Delphi

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.

like image 261
rhody Avatar asked Jun 04 '13 18:06

rhody


People also ask

How do you call a procedure in Delphi?

You can make the call using the declared name of the routine (with or without qualifiers) or using a procedural variable that points to the routine. In either case, if the routine is declared with parameters, your call to it must pass parameters that correspond in order and type to the parameter list of the routine.

How do I declare a function in Delphi?

Declaring Procedures and Functions. When you declare a procedure or function, you specify its name, the number and type of parameters it takes, and, in the case of a function, the type of its return value; this part of the declaration is sometimes called the prototype, heading, or header.

What is Stdcall in Delphi?

Description. The stdcall directive uses the Windows standard calling convention: arguments are pushed onto the stack, starting with the rightmost argument. The subroutine is responsible for popping the arguments from the stack.


2 Answers

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.

like image 70
Rob Kennedy Avatar answered Oct 27 '22 20:10

Rob Kennedy


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.

like image 29
rhody Avatar answered Oct 27 '22 21:10

rhody