我需要从 Delphi 调用一些 C++ 代码。作为返回,C++ 代码需要能够回调到 Delphi 代码中。此处显示的示例 Calling a callback function in Delphi from a C++ DLL工作得很好。但是,我不想将单个 Delphi 函数作为回调传递给 C++,而是传递一个实现接口(interface)的 Delphi 对象。
编辑:通过接口(interface),我指的是 C++ 术语,它是一个具有纯虚函数的类。这不一定是用 Delphi interface
关键字定义的类型。换句话说,下面的类定义了一个我想从 C++ 调用的接口(interface):
ICallable = class
procedure callMe stdcall; virtual; abstract;
procedure CallMeAgain stdcall; virtual; abstract;
end;
ICallable
接口(interface)将依次在 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 接口(interface):
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 类没有与C++ 类兼容的VMT。我认为这是因为所有 Delphi 类都派生自声明了虚方法的 TObject
。这些虚拟方法出现在 VMT 中 我假设这些方法将首先出现在 VMT 中。然而,编译器安排 TObject
的内置虚拟方法在 VMT 中具有负索引。这意味着用户定义的虚拟方法(那些在 TObject
的子类中定义的)从索引 0 开始。
这意味着问题代码中的 Delphi 和 C++ 类确实具有兼容的 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 接口(interface),而不是依赖于此类实现细节。但是,如您所知,这些是 COM 接口(interface),因此您必须实现 IUnknown
。你说你想避开 COM 机制,但你唯一需要添加的是 IUnknown
。在我看来,这并不是特别繁重。我觉得您认为您需要注册 CLSID、实现类工厂等等。你不知道。您只需要实现 IUnknown
。
无论如何,如果您真的打算避免使用 IUnknown
,那么您就不能使用 Delphi 接口(interface),据我所知有两个选择:
- 在您的 Delphi 代码中手动实现 VMT。 VMT 只是一个函数指针数组。这将引导您编写看起来像 COM 在 C 中的方式的代码。完全可能,但并不完全令人愉快。
- 使用您问题中概述的方法,并依赖于
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;
}
输出
CallMe CallMeAgain TRUE
关于c++ - Delphi 和 C++ 类 VMT 兼容吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36167559/