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

Big*_*ion 3 c++ delphi delphi-7

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

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

ICallable = class 
    procedure callMe stdcall; virtual; abstract;
    procedure CallMeAgain stdcall; virtual; abstract;
end;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

在C++端,编译为DLL,我想定义ICallable接口,如下所示:

class ICallable{
public:
  virtual void callMe()=0;
  virtual void callMeAgain()=0;
}
Run Code Online (Sandbox Code Playgroud)

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

#define DllExport   extern "C" __declspec( dllexport )

DLLExport bool Callback(ICallable* callable){
   callable->callMe();
   callable->callMeAgain();
   return true;
}  
Run Code Online (Sandbox Code Playgroud)

最后回到Delphi:

function Callback(myCallable: ICallable) : Boolean cdecl; external 'dllname'
Run Code Online (Sandbox Code Playgroud)

题:

  • 这只有 C++和Delphi以相同方式实现其虚方法表时才有效.是这样的吗?

Dav*_*nan 11

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

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

这意味着问题代码中的Delphi和C++类确实具有兼容的VMT.我相信这个设计选择是为了支持早期版本的Delphi中的COM.为了支持我的主张,我建议您阅读文档,并强调:

VMT的布局如下表所示.在32位平台上,在正偏移处,VMT由一个32位方法指针列表(64位平台上的64位方法指针)组成 - 每个用户定义的类型中的虚拟方法 - - 按照声明的顺序.每个槽包含虚拟方法的相应入口点的地址.此布局与C++ v表和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索引的内置虚拟方法的实现细节.

可编译代码选项2如下所示:

德尔福

{$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.
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

产量

CallMe
CallMeAgain
TRUE