将C头中的__declspec转换为Delphi

时间:2011-10-04 22:24:35

标签: c++ delphi dll interface declspec

我在将一个类从C头转换为在Delphi中使用时遇到了麻烦。

C头文件中声明的片段如下所示:

class __declspec(uuid("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"))
ISomeInterface
{    
public:
  virtual
  BOOL
  SomeBoolMethod(
    VOID
  ) const = 0;
}

我正在编写一个DLL,它导出一个接受ISomeInterface参数的方法,例如

function MyExportFunc (pSomeInterface: ISomeInterface): Cardinal; export; stdcall;
var
  aBool: BOOL;
begin
  aBool := pSomeInterface.SomeBoolMethod;
end;

我在Delphi中声明了ISomeInterface,如下所示:

type ISomeInterface = class
  function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
end;

调用pSomeInterface.SomeBoolMethod会导致访问冲突。

我做了一些根本错误的事情吗?

实际的C头是httpserv.h,我正在尝试在Delphi中实现IIS7本机模块。

一些有效的c ++代码如下所示:

HRESULT
__stdcall
RegisterModule(
    DWORD                           dwServerVersion,
    IHttpModuleRegistrationInfo *   pModuleInfo,
    IHttpServer *                   pHttpServer
)
{
  // etc
}

调试时,我看到pModuleInfo参数包含一个__vfptr成员,其下面有6个成员(名为[0]到[5]并且地址为值),我推断它们是IHttpModuleRegistrationInfo类中虚拟方法的指针。

Delphi RegisterModule导出现在如下所示:

function RegisterModule (dwServerVersion: DWORD; var pModuleInfo: Pointer; var pHttpServer: Pointer): HRESULT; export; stdcall;
begin
  // etc
end;

pModuleInfo包含cpp示例中__vfptr成员的等效地址,并假设__vfptr中的顺序与头文件中的类声明相同,我提取方法地址:

function RegisterModule (dwServerVersion: DWORD; var pModuleInfo: Pointer; var pHttpServer: Pointer): HRESULT; export; stdcall;
var
  vfptr: Pointer;
  ptrGetName: Pointer;
  ptrGetId: Pointer;
begin
  vfptr := pModuleInfo;
  ptrGetName := Pointer (Pointer (Cardinal(vfptr))^);
  ptrGetId := Pointer (Pointer (Cardinal(vfptr) + 4)^);
end;

我现在有方法地址要调用,所以现在我只需要以某种方式调用它。我可能会以错误的方式解决这个问题!

3 个答案:

答案 0 :(得分:3)

不要声明为cdecl。 32位COM方法的调用约定是C引用的stdcall,别名为CALLBACK和WINAPI。看看Delphi是否有一个等价的。如果Delphi支持COM,则它有一个。

另外,确保C接口不是从IUnknown派生的 - 几乎所有COM接口都是这样做的。如果是的话,从Delphi等价物派生你的界面。

另外,我不确定Delphi中的对象布局是否与COM的对象布局(虚拟函数指针表作为第一个数据项)相匹配。再次,找到Delphi实现COM的方式。

答案 1 :(得分:2)

在Delphi中,无论您是否指定,所有类都派生自TObject。这意味着以下Delphi声明:

type
  ISomeInterface = class
    function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
  end;

与此相同:

type
  ISomeInterface = class(TObject)
    function SomeBoolMethod: BOOL; cdecl; virtual; abstract;
  end;

这使得Delphi中ISomeInterface类的VMT与C ++中ISomeInterface类的VMT不同,这可能导致AV。

同样,正如其他人所提到的,将Delphi ISomeInterface类型声明为interface而不是class将从IUnknown隐式地派生出来,而C ++类是也没做。

简而言之,Delphi根本无法重现C ++可以使用的普通类型类型。 Delphi中最接近的(不改变C ++代码以使用与Delphi更兼容的东西)是声明record类型而不是class类型或interface类型。但是,由于正在使用虚拟方法,因此必须在Delphi中手动重现C ++ VMT,并且必须声明所有方法都具有this(Delphi中的Self)指针的显式参数。

否则,将C ++代码和Delphi代码切换为使用COM接口,这是一个二进制标准,因此两种语言将相互兼容。

答案 2 :(得分:1)

__declspec(uuid将一个UUID附加到类定义,以便编译器可以在__uuidof operator请求时将其应用于代码中的其他位置。

也就是说,如果你移植到Delphi,你可能基本上可以从类中省略这个规范。至于接口,当你用Delphi声明一个接口时,你肯定有机会为这个定义提供一个IID。

但是,

代码中的访问冲突与__declspec并不完全相关。您的C ++ ISomeInterface不完全是一个接口,因为它不是从IUnknown继承的。 IIRC,使用Delphi,您无法声明此类接口,无论您声明什么必须至少从IUnknown派生。所以没有办法安全轻松地转换接口(确切地说,是类 - 因为它不是有效的COM接口定义)。

以下是正确完成的匹配/转换:

MIDL_INTERFACE("56a86897-0ad4-11ce-b03a-0020af0ba770")
IReferenceClock : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetTime( 
        /* [out] */ REFERENCE_TIME *pTime) = 0;
// ...

来自http://code.google.com/p/dspack/source/browse/trunk/src/DirectX9/DirectSound.pas#456

的Delphi双胞胎
type
  IReferenceClock = interface(IUnknown)
    ['{56a86897-0ad4-11ce-b03a-0020af0ba770}']
    // IReferenceClock methods
    function GetTime(out pTime: TReferenceTime): HResult; stdcall;
// ...