Remko
Remko

Reputation: 7340

Convert variable arguments in C stdcall interface method to Delphi

I am converting the ICallFrame interface to Delphi and I am not sure how to handle the ICallFrame::Invoke method.

This is the definition:

HRESULT Invoke(
  void *pvReceiver,
  ...  
);

Per documentation the varargs directive works only with external routines and only with the cdecl calling convention.

It's a bit strange as Invoke is declared with STDMETHODCALLTYPE thus __stdcall, which is a callee-clean convention. But a variadic function cannot be callee-clean since the callee does not know how many parameters were passed. Is there some kind of compiler magic if you do this with Visual Studio?

Even if compiler magic uses cdecl, the Delphi compiler doesn't accept this (because it's not external) E2070 Unknown directive: 'varargs'

ICallFrame = interface(IUnknown)
  ['{D573B4B0-894E-11d2-B8B6-00C04FB9618A}']
  // other methods left out
  function Invoke(pvReceiver: PVOID): HRESULT; cdecl; varargs;
end;

Based on Rudy's answer it seems this could work:

type
    TfnInvoke = function(this: Pointer; pvReceiver: Pointer): HRESULT; cdecl varargs;

implementation 

function GetInterfaceMethod(const intf; methodIndex: dword) : pointer;
begin
  Result := Pointer(pointer(dword_ptr(pointer(intf)^) + methodIndex * SizeOf(Pointer))^);
end;
...
var
  Invoker: TfnInvoke;
  ...
  // Don't forget IUnknown methods when counting!
  Invoker := GetInterfaceMethod(myIntf, 21);
  Invoker(myIntf, FObject, 11, 17.3);

I will test with this and report back... EDIT: tested and works.

Upvotes: 3

Views: 678

Answers (1)

Rudy Velthuis
Rudy Velthuis

Reputation: 28836

Stdcall cannot use variable arguments. Any C or C++ function taking variable arguments must be __cdecl, because only in that calling convention, the caller (code), which knows how many parameters were passed, cleans up the stack. Even if the default calling convention is stdcall, var args functions will be cdecl.

Delphi can not generate functions like that, but it can consume existing external C functions (in a DLL or .obj file) using the varargs directive.

So a C (or C++) function like

HRESULT Invoke(void *pvReceiver, ...);

should be converted as:

function Invoke(pvReceiver: Pointer); cdecl; varargs;

Note that you leave out the ... part in the Delphi declaration. It is assumed when you use the varargs directive.

And that allows you to use it in Delphi like in C or C++:

Invoke(someReceiver, 17, 1.456);

Another example: for instance the well known C function printf():

int printf(const char *format, ...);

is declared as

function printf(format: PAnsiChar {args}): Integer; cdecl; varargs;

in System.Win.Crtl.pas.

Update

This seems to be a method of an interface.

Not sure if that works, but I would try:

type
  TInvoke = function(Intf: IInterface; pvReceiver: Pointer): HRESULT; cdecl varargs; 
  // No semicolon between cdecl and varargs.

and use it as:

MyResult := TInvoke(@myIntf.Invoke)(myIntf, someReceiver, 11, 17.3);

This method may have been declared as __stdcall (through the STDMETHODCALLTYPE macro), but even then, if it is variadic, the (VC++) compiler will generate __cdecl, as Remy Lebeau found in Raymond Chen's blog.

Upvotes: 4

Related Questions