I need to call some C++ code from Delphi. The C++ code needs to be able to callback into the Delphi code in return. The example shown here Calling a callback function in Delphi from a C++ DLL works perfectly well. However, instead of passing to the C++ a single Delphi function as a callback, I would like to pass a Delphi object implementing an interface.
Edit: By interface I am referring to the C++ terminology, which is a class with pure virtual functions. This is not necessarily a type defined with the Delphi interface
keyword. In other words, the following class defines an interface I would like to call from C++:
ICallable = class
procedure callMe stdcall; virtual; abstract;
procedure CallMeAgain stdcall; virtual; abstract;
end;
The ICallable
interface would in turn be implemented in Delphi as follows:
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;
On the C++ side, which is compiled as a DLL, I want to define the ICallable interface as follows:
class ICallable{
public:
virtual void callMe()=0;
virtual void callMeAgain()=0;
}
And export the following DLL function so that it can be called by Delphi:
#define DllExport extern "C" __declspec( dllexport )
DLLExport bool Callback(ICallable* callable){
callable->callMe();
callable->callMeAgain();
return true;
}
And finally back in Delphi:
function Callback(myCallable: ICallable) : Boolean cdecl; external 'dllname'
Question:
This can only work of C++ and Delphi implement their virtual method tables in the same way. Is it the case?
I originally thought that a Delphi class does not have a VMT compatible with a C++ class. I thought that because all Delphi classes derive from TObject
which declares virtual methods. These virtual methods appear in the VMT I assumed that these methods would appear first in the VMT. However, it transpires that the compiler arranges that the built in virtual methods of TObject
have negative indices in the VMT. Which means that the user-defined virtual methods (those define in the subclasses of TObject
) start from index 0.
This means that the Delphi and C++ classes in your code in the question do indeed have compatible VMTs. I believe that this design choice was made to support COM in earlier versions of Delphi. To back up my claims, I refer you to the documentation says, with my emphasis:
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.
It should be stressed that nothing in the C++ standard mandates the use of VMTs for virtual methods, even less how VMTs are implemented. In reality, every mainstream Windows compiler has VMTs implemented this way in order to support COM.
Rather than relying on such implementation details, you could use Delphi interfaces. However, as your know, these are COM interfaces and so you must implement IUnknown
. You say you want to avoid the machinery of COM, but the only thing you need to add is IUnknown
. That is not especially onerous in my view. I get the sense that you think you need to register CLSIDs, implement class factories and so on. You don't. You would just need to implement IUnknown
.
Anyway, if you really are set on avoiding IUnknown
then you cannot use Delphi interfaces and have two options so far as I can tell:
TObject
uses negative VMT indices for its built in virtual methods.Compilable for code option 2 looks like this:
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;
}
Output
CallMe CallMeAgain TRUE
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With