将C语言stdcall接口方法中的可变参数转换为Delphi。

3
我将ICallFrame接口转换为Delphi,但不确定如何处理ICallFrame::Invoke方法。
这是定义:
HRESULT Invoke(
  void *pvReceiver,
  ...  
);

根据文档,varargs 指令仅适用于外部例程和 cdecl 调用约定。这有点奇怪,因为 Invoke 声明为 STDMETHODCALLTYPE,即 __stdcall,这是一种被调用者清理的约定。但是可变参数函数不能是被调用者清理的,因为被调用者不知道传递了多少参数。如果使用 Visual Studio 进行此操作,是否存在某种编译器魔法?即使编译器魔法使用 cdecl,Delphi 编译器也不接受此操作(因为它不是外部函数)E2070 Unknown directive: 'varargs'
ICallFrame = interface(IUnknown)
  ['{D573B4B0-894E-11d2-B8B6-00C04FB9618A}']
  // other methods left out
  function Invoke(pvReceiver: PVOID): HRESULT; cdecl; varargs;
end;

根据Rudy的回答,似乎这个方法可行:

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

我会用这个进行测试并回报结果... 编辑:已测试并且可行。

1个回答

4

Stdcall不能使用可变参数。任何接受可变参数的C或C++函数必须是__cdecl,因为只有在这种调用约定中,调用者(代码)知道传递了多少个参数并清理堆栈。即使默认调用约定是stdcall,var args函数也将是cdecl

Delphi无法生成这样的函数,但可以使用varargs指令消费现有的外部C函数(位于DLL或.obj文件中)。

因此,像下面这样的C(或C++)函数

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

应该被转换为:

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

请注意,在Delphi声明中省略...部分。在使用varargs指令时,这一点是默认的。
这使您能够像在C或C ++中一样在Delphi中使用它:
Invoke(someReceiver, 17, 1.456);

另一个例子:比如广为人知的C语言函数printf()

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

被声明为

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

System.Win.Crtl.pas 文件中。

更新

这似乎是一个接口的方法。

不确定是否有效,但我会尝试:

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

并将其用作:

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

即使该方法已通过STDMETHODCALLTYPE宏声明为__stdcall,但如果它是可变参数的话,(VC++)编译器仍将生成__cdecl,正如Remy Lebeau在Raymond Chen的博客中发现的那样。


1
@Remko:但是这应该可以运行(在cdecl和varargs之间没有“;”):type TfnInvoke = function(this, pvReceiver: Pointer): HRESULT; cdecl varargs;。它在Delphi Tokyo中可用。 - Rudy Velthuis
1
如果您查看 callobj.hICallFrame::Invoke() 的实际 SDK 声明,它是使用 STDMETHODCALLTYPE 声明的,这解析为 __stdcall。因此,我不明白 varargs 如何能够与此接口配合使用。通常情况下,varargs 完全不兼容 COM,因此我不知道 ICallFrame 的设计者在想什么。 - Remy Lebeau
2
如果您尝试使用不兼容的调用约定声明变参函数,编译器会将其秘密地转换为cdecl。 - Remy Lebeau
1
事实是,stdcall 简单地不支持可变参数,就这样。只有 cdecl 支持。因此,如果编译器在参数列表末尾看到 ...,它会强制使用 cdecl 调用约定。忽略关于 this 参数的部分,它与此无关。 - Remy Lebeau
1
stdcallcdecl以相同的方式传递参数值,但是它们以不同的方式清理参数。因此,使用正确的调用约定非常重要,否则将会破坏调用栈。 - Remy Lebeau
显示剩余9条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接