【问题标题】:Are Delphi and C++ class VMTs compatible?Delphi 和 C++ 类 VMT 是否兼容?
【发布时间】:2016-03-23 00:09:43
【问题描述】:

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

编辑:通过接口我指的是 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;

在编译为DLL的C++端,我想定义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 以相同的方式实现它们的虚拟方法表。是这样吗?

【问题讨论】:

  • Delphi 接口遵循 COM 约定。所以你需要遵循 C++ 中的 COM 约定,参见:rvelthuis.de/articles/articles-cppobjs.htmlscritub.com/stiinta/tutorials/visual-c-en/…
  • 哦,Delphi 中的接口是使用interface 关键字声明的。如果您坚持声明非 COM 接口,请使用 abstract class。接口的实现派生自TInterfacedObject
  • Delphi 总是将 VMT 作为类中的第一个字段。 C++ 没有这样的规则。所有这些都可以在 Rudy 的文章中读到。 你读过吗?所以答案是肯定的和否定的。
  • VMT 只是一个指针数组,无论是在 Delphi 还是 C++ 中;这里没有问题。你的代码还有其他明显的问题,首先它没有实现IUnknown。见Consuming Delphi interfaces in Dephi and C++如何解决相反的问题——写一个Delphi DLL导出一个接口并在C++端使用这个接口。
  • COM 机制只是你做以下事情: 1. 使用 Delphi 接口。 2.在Delphi代码中实现IInterface,例如通过派生自TInterfacedObject。 3. 从IUnknown 派生C++ 类。 4. 呃,就这样,没有了。

标签: c++ delphi delphi-7


【解决方案1】:

这只能在 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 索引的实现细节。

可编译代码选项 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.

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;
}

输出

打给我 再给我打过来 真的

【讨论】:

  • 这让我很开心!接受你的回答。感谢您花时间解释有关 Delphi/C++ VMT 的所有细节
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-14
  • 1970-01-01
  • 2013-06-06
  • 2015-10-03
相关资源
最近更新 更多