Delphi和C++类的VMT兼容吗?

4
我需要从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;
< p > 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;
}  

最后回到Delphi:

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

问题:

  • 只有当C++和Delphi以相同的方式实现它们的虚方法表时,这才能起作用。情况是否如此?

3
Delphi接口遵循COM规范。因此,在C++中,您需要遵循COM规范,请参见:http://rvelthuis.de/articles/articles-cppobjs.html 和 http://www.scritub.com/stiinta/tutorials/visual-c-en/Sharing-Code-and-Objects-Betwe18279520.php。 - Johan
2
Delphi中类的第一个字段始终为VMT。C++没有这样的规定。所有这些都可以在Rudy的文章中阅读。你读过了吗?因此,答案是YES和NO。 - Johan
1
VMT只是指针数组,在Delphi或C++中都没有问题。你的代码还有其他明显的问题,首先它没有实现"IUnknown"接口。请参考在Delphi和C++中使用Delphi接口解决相反的问题——编写一个导出接口的Delphi DLL,并在C++端使用该接口。 - kludg
@CraigYoung请查看已接受的答案,以获取不需要“展平”处理的解决方案。 - BigONotation
1
COM机制就是简单地执行以下步骤:1. 使用Delphi接口。2. 在Delphi代码中实现IInterface,例如通过从TInterfacedObject派生。3. 从IUnknown派生C++类。4. 嗯,就这样了,没有更多的了。 - David Heffernan
显示剩余8条评论
1个回答

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-table和COM兼容。在负偏移处,VMT包含一些对Delphi实现内部有用的字段。应用程序应使用TObject中定义的方法查询此信息,因为布局可能会在未来的Delphi语言实现中发生变化。
需要强调的是,C++标准中没有规定必须使用VMT来处理虚拟方法,更不用说如何实现VMT了。实际上,每个主流的Windows编译器都是这样实现VMT以支持COM。
不必依赖这些实现细节,您可以使用Delphi接口。但是,正如您所知,这些是COM接口,因此您必须实现IUnknown。您说您想避免COM的机制,但您需要添加的唯一内容就是IUnknown。在我看来,这并不特别繁琐。我感觉您认为您需要注册CLSIDs、实现类工厂等等。你不用这样做。您只需要实现IUnknown即可。
无论如何,如果您确实要避免IUnknown,那么您不能使用Delphi接口,并且根据我所知,您有两个选择:
1. 在您的Delphi代码中手动实现VMT。VMT只是一个函数指针数组。这会使您的代码看起来像C中的COM。完全可以实现,但不是很愉快。
2. 使用您问题中概述的方法,并依赖TObject使用其内置虚拟方法的负VMT索引的实现细节。
选项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
真

1
这让我的一天充满了喜悦!接受你的回答。感谢你花时间解释有关Delphi/C++ VMTs的所有细节。 - BigONotation

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