3

我在将类从 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;

我现在有了要调用的方法地址,所以现在我只需要以某种方式调用它。不过,我可能会走错路!

4

3 回答 3

3

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

此外,请确保 C 接口不是从 IUnknown 派生的 - 实际上所有 COM 接口都是如此。如果是,请从 Delphi 等价物派生您的界面。

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

于 2011-10-04T22:38:40.793 回答
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;

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

同样,正如其他人所提到的,将 DelphiISomeInterface类型声明为 aninterface而不是 aclass将隐含地从 中派生它IUnknown,而 C++ 类也没有这样做。

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

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

于 2011-10-05T00:31:06.273 回答
1

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

也就是说,如果你要移植到 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;
// ...
于 2011-10-04T22:37:55.190 回答