Delphi和C ++类VMT是否兼容?

时间:2016-03-23 00:09:44

标签: c++ delphi delphi-7

我需要从Delphi调用一些C ++代码。 C ++代码需要能够回调到Delphi代码中。此处显示的示例Calling a callback function in Delphi from a C++ DLL非常有效。但是,我想传递一个实现接口的Delphi对象,而不是将单个Delphi函数作为回调传递给C ++。

编辑:通过接口,我指的是C ++术语,它是一个具有纯虚函数的类。这不一定是使用Delphi interface关键字定义的类型。换句话说,下面的类定义了我想从C ++调用的接口:

ICallable = class 
    procedure callMe stdcall; virtual; abstract;
    procedure CallMeAgain stdcall; virtual; abstract;
end;

ICallable接口将依次在Delphi中实现,如下所示:

MyCallable = class(ICallable)
   procedure callMe override;
   procedure callMeAgain override;
end;

procedure MyCallable.callMe
begin
   WriteLine('I was called');
end;

procedure MyCallable.callMeAgain
begin
   WriteLine('I was called again');
end;

在C ++端,编译为DLL,我想按如下方式定义ICallable接口:

class ICallable{
public:
  virtual void callMe()=0;
  virtual void callMeAgain()=0;
}

并导出以下DLL函数,以便Delphi可以调用它:

#define DllExport   extern "C" __declspec( dllexport )

DLLExport bool Callback(ICallable* callable){
   callable->callMe();
   callable->callMeAgain();
   return true;
}  

最后回到德尔福:

function Callback(myCallable: ICallable) : Boolean cdecl; external 'dllname'

问题:

  • 如果 C ++和Delphi以相同的方式实现其虚拟方法表,那么这只能工作。是这种情况吗?

1 个答案:

答案 0 :(得分:11)

  

这只能用于C ++和Delphi以相同的方式实现其虚拟方法表。是这样的吗?

我原本以为Delphi类没有与C ++类兼容的VMT。我认为这是因为所有Delphi类派生自TObject,它声明了虚方法。这些虚拟方法出现在VMT中我假设这些方法首先出现在VMT中。但是,发现编译器安排TObject的内置虚拟方法在VMT中具有负索引。这意味着用户定义的虚拟方法(在TObject的子类中定义的那些)从索引0开始。

这意味着问题代码中的Delphi和C ++类确实具有兼容的VMT。我相信这个设计选择是为了支持早期版本的Delphi中的COM。为了支持我的主张,我建议您documentation说,重点是:

  

VMT的布局如下表所示。在32位平台上,在正偏移处,VMT由一个32位方法指针列表(64位平台上的64位方法指针)组成 - 每个用户定义的类型中的虚拟方法 - - 按照声明的顺序。每个槽包含虚拟方法的相应入口点的地址。此布局与C ++ v-table 和COM兼容。在负偏移处,VMT包含许多Delphi实现内部的字段。应用程序应使用TObject中定义的方法来查询此信息,因为在将来的Delphi语言实现中布局可能会发生变化。

应该强调的是,C ++标准中没有任何内容要求将VMT用于虚拟方法,更不用说实现VMT的方式。实际上,每个主流Windows编译器都有这样实现的VMT,以支持COM。

您可以使用Delphi接口,而不是依赖此类实现细节。但是,如您所知,这些是COM接口,因此您必须实现IUnknown。你说你想避免COM的机制,但你唯一需要添加的是IUnknown。在我看来,这并不是特别繁重。我觉得你认为你需要注册CLSID,实现类工厂等等。你没有。您只需要实现IUnknown

无论如何,如果你真的开始避免使用IUnknown那么你就不能使用Delphi接口,并且我可以告诉你有两个选择:

  1. 在Delphi代码中手动实现VMT。 VMT只是一个函数指针数组。这将导致您看起来像COM在C中所做的方式的代码。完全可能,但不是很愉快。
  2. 使用您问题中列出的方法,并依赖TObject为其内置虚拟方法使用负VMT索引的实现细节。
  3. 可编译代码选项2如下所示:

    <强>的Delphi

    {$APPTYPE CONSOLE}
    
    type
      ICallable = class
      public
        procedure CallMe cdecl; virtual; abstract;
        procedure CallMeAgain cdecl; virtual; abstract;
      end;
    
      MyCallable = class(ICallable)
      public
        procedure CallMe; override;
        procedure CallMeAgain; override;
      end;
    
    procedure MyCallable.CallMe;
    begin
      Writeln('CallMe');
    end;
    
    procedure MyCallable.CallMeAgain;
    begin
      Writeln('CallMeAgain');
    end;
    
    const
      dllname = 'C:\Users\heff\Desktop\Win32Project1\Debug\Win32Project1.dll';
    
    function Callback(Callable: ICallable): Boolean; cdecl; external dllname;
    
    var
      Callable: ICallable;
    
    begin
      Callable := MyCallable.Create;
      Writeln(Callback(Callable));
      Callable.Free;
      Readln;
    end.
    

    <强> C ++

    #include <Windows.h>
    
    BOOL APIENTRY DllMain( HMODULE hModule,
                           DWORD  ul_reason_for_call,
                           LPVOID lpReserved
                         )
    {
        switch (ul_reason_for_call)
        {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
        case DLL_THREAD_DETACH:
        case DLL_PROCESS_DETACH:
            break;
        }
        return TRUE;
    }
    
    class ICallable
    {
    public:
        virtual void CallMe() = 0;
        virtual void CallMeAgain() = 0;
    };
    
    extern "C" __declspec(dllexport) bool Callback(ICallable* callable)
    {
        callable->CallMe();
        callable->CallMeAgain();
        return true;
    }
    

    <强>输出

    CallMe
    CallMeAgain
    TRUE