Delphi 和 C++ class VMT 兼容吗?
Are Delphi and C++ class VMTs compatible?
我需要从 Delphi 调用一些 C++ 代码。 C++ 代码需要能够回调到 return 中的 Delphi 代码。此处显示的示例 Calling a callback function in Delphi from a C++ DLL 运行良好。但是,我不想将单个 Delphi 函数作为回调传递给 C++,而是想传递一个实现接口的 Delphi 对象。
编辑:通过接口,我指的是 C++ 术语,它是具有纯虚函数的 class。这不一定是使用 Delphi interface
关键字定义的类型。换句话说,下面的 class 定义了一个我想从 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 以相同的方式实现它们的虚拟方法表时才有效。是这样吗?
This can only work of C++ and Delphi implement their virtual method tables in the same way. Is it the case?
我原本以为 Delphi class 没有与 C++ class 兼容的 VMT。我认为这是因为所有 Delphi classes 都派生自 TObject
,它声明了虚拟方法。这些虚拟方法出现在 VMT 中 我假设这些方法将首先出现在 VMT 中。但是,编译器安排 TObject
的内置虚拟方法在 VMT 中具有负索引。这意味着用户定义的虚拟方法(在 TObject
的子 class 中定义的方法)从索引 0 开始。
这意味着问题代码中的 Delphi 和 C++ classes 确实具有兼容的 VMT。我相信这种设计选择是为了在 Delphi 的早期版本中支持 COM。为了支持我的说法,我建议你参考 documentation 所说的,我强调:
The layout of a VMT is shown in the following table. On the 32-bit platforms, at positive offsets, a VMT consists of a list of 32-bit method pointers (64-bit method pointers on the 64-bit platform)--one per user-defined virtual method in the class type--in order of declaration. Each slot contains the address of the corresponding entry point of the virtual method. This layout is compatible with a C++ v-table and with COM. At negative offsets, a VMT contains a number of fields that are internal to Delphi's implementation. Applications should use the methods defined in TObject to query this information, since the layout is likely to change in future implementations of the Delphi language.
应该强调的是,C++ 标准中没有任何内容强制要求对虚拟方法使用 VMT,更不用说 VMT 的实现方式了。实际上,每个主流 Windows 编译器都以这种方式实现了 VMT 以支持 COM。
您可以使用 Delphi 接口,而不是依赖此类实现细节。但是,如您所知,这些是 COM 接口,因此您必须实现 IUnknown
。你说你想避免 COM 的机制,但你唯一需要添加的是 IUnknown
。在我看来,这并不是特别繁重。我觉得您认为您需要注册 CLSID、实施 class 工厂等等。你不知道。您只需要实施 IUnknown
。
无论如何,如果你真的打算避免 IUnknown
那么你就不能使用 Delphi 界面,据我所知有两个选择:
- 在您的 Delphi 代码中手动实施 VMT。 VMT 只是一个函数指针数组。这将引导您编写看起来像 COM 在 C 中的方式的代码。完全可能,但并不完全令人愉快。
- 使用您问题中概述的方法,并依赖
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
TRUE
我需要从 Delphi 调用一些 C++ 代码。 C++ 代码需要能够回调到 return 中的 Delphi 代码。此处显示的示例 Calling a callback function in Delphi from a C++ DLL 运行良好。但是,我不想将单个 Delphi 函数作为回调传递给 C++,而是想传递一个实现接口的 Delphi 对象。
编辑:通过接口,我指的是 C++ 术语,它是具有纯虚函数的 class。这不一定是使用 Delphi interface
关键字定义的类型。换句话说,下面的 class 定义了一个我想从 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 以相同的方式实现它们的虚拟方法表时才有效。是这样吗?
This can only work of C++ and Delphi implement their virtual method tables in the same way. Is it the case?
我原本以为 Delphi class 没有与 C++ class 兼容的 VMT。我认为这是因为所有 Delphi classes 都派生自 TObject
,它声明了虚拟方法。这些虚拟方法出现在 VMT 中 我假设这些方法将首先出现在 VMT 中。但是,编译器安排 TObject
的内置虚拟方法在 VMT 中具有负索引。这意味着用户定义的虚拟方法(在 TObject
的子 class 中定义的方法)从索引 0 开始。
这意味着问题代码中的 Delphi 和 C++ classes 确实具有兼容的 VMT。我相信这种设计选择是为了在 Delphi 的早期版本中支持 COM。为了支持我的说法,我建议你参考 documentation 所说的,我强调:
The layout of a VMT is shown in the following table. On the 32-bit platforms, at positive offsets, a VMT consists of a list of 32-bit method pointers (64-bit method pointers on the 64-bit platform)--one per user-defined virtual method in the class type--in order of declaration. Each slot contains the address of the corresponding entry point of the virtual method. This layout is compatible with a C++ v-table and with COM. At negative offsets, a VMT contains a number of fields that are internal to Delphi's implementation. Applications should use the methods defined in TObject to query this information, since the layout is likely to change in future implementations of the Delphi language.
应该强调的是,C++ 标准中没有任何内容强制要求对虚拟方法使用 VMT,更不用说 VMT 的实现方式了。实际上,每个主流 Windows 编译器都以这种方式实现了 VMT 以支持 COM。
您可以使用 Delphi 接口,而不是依赖此类实现细节。但是,如您所知,这些是 COM 接口,因此您必须实现 IUnknown
。你说你想避免 COM 的机制,但你唯一需要添加的是 IUnknown
。在我看来,这并不是特别繁重。我觉得您认为您需要注册 CLSID、实施 class 工厂等等。你不知道。您只需要实施 IUnknown
。
无论如何,如果你真的打算避免 IUnknown
那么你就不能使用 Delphi 界面,据我所知有两个选择:
- 在您的 Delphi 代码中手动实施 VMT。 VMT 只是一个函数指针数组。这将引导您编写看起来像 COM 在 C 中的方式的代码。完全可能,但并不完全令人愉快。
- 使用您问题中概述的方法,并依赖
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 TRUE