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:
- Implement the VMT manually in your Delphi code. A VMT is just an array of function pointers. This would lead you to code that looks the way COM does in C. Perfectly possible, but not exactly pleasant.
- Use the approach outlined in your question, and rely on the implementation detail that
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