4

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 if C++ and Delphi implement their virtual method tables in the same way. Is this the case?
Community
  • 1
  • 1
BigONotation
  • 4,406
  • 5
  • 43
  • 72
  • 3
    Delphi interfaces follow the COM conventions. So you need to follow COM conventions in C++, see: http://rvelthuis.de/articles/articles-cppobjs.html and http://www.scritub.com/stiinta/tutorials/visual-c-en/Sharing-Code-and-Objects-Betwe18279520.php – Johan Mar 23 '16 at 00:41
  • Oh and interfaces in Delphi are declared using the `interface` keyword. If you insist on declaring a Non-COM interface you use `abstract class`. The implementation of an interface derives from `TInterfacedObject`. – Johan Mar 23 '16 at 00:46
  • @Johan I am trying to explicitly avoid the whole COM machinery here. And also in my case I am not designing C++ COM component. I want to be able to callback to a Delphi object "sharing" the same "interface" with C++. In essence I want to know if a Delphi class with virtual abstract methods maps to a corresponding C++ class with pure virtual functions – BigONotation Mar 23 '16 at 00:53
  • @Johan Please read my edit at the top of the page explaining why this is not a duplicate – BigONotation Mar 23 '16 at 01:20
  • IMO Rudy's article has all the answers to your question. – Johan Mar 23 '16 at 01:24
  • @Johan My question: are Delphi & C++ virtual method tables compatible? Yes? No? The linked answer does not answer that. – BigONotation Mar 23 '16 at 01:37
  • 2
    Delphi always has the VMT as the first field in a class. C++ has no such rule. All this can be read in Rudy's article. *have you read it?* So the answer is YES and NO. – Johan Mar 23 '16 at 01:45
  • 1
    VMT is just an array of pointers, both in Delphi or C++; there are no problems here. Your code has other obvious problems, first of all it does not implement `IUnknown`. See [Consuming Delphi interfaces in Dephi and C++](https://sergworks.wordpress.com/2013/12/13/consuming-delphi-interfaces-in-dephi-and-c/) how to solve the opposite problem - write a Delphi DLL exporting an interface and use this interface on C++ side. – kludg Mar 23 '16 at 04:27
  • @BigLudinski As the answer in the duplicate question indicates: If you "want to avoid the COM machinery", you can 'flatten' the interface into C callable functions. It's really trivial to do: expose a DLL interface that mirrors the object methods but takes a handle to an object instance as the first parameter. (It's partly how OO works under the hood in any case.) – Disillusioned Mar 23 '16 at 05:28
  • @CraigYoung See accepted answer for a solution without "flattening". – BigONotation Mar 23 '16 at 13:15
  • @BigLudinski I have seen David's answer. It doesn't change the fact that 'flattening' _can be used_ if you want to avoid the COM machinery. – Disillusioned Mar 23 '16 at 13:20
  • 1
    COM machinery is simply that you do the following: 1. Use Delphi interfaces. 2. Implement `IInterface` in the Delphi code, e.g. by deriving from `TInterfacedObject`. 3. Derive the C++ class from `IUnknown`. 4. Er, that's it, there is no more. – David Heffernan Mar 23 '16 at 17:33
  • @DavidHeffernan OK I will give it a try. – BigONotation Mar 23 '16 at 17:38

1 Answers1

11

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:

  1. 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.
  2. 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
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • 1
    This made my day! Accepting your answer. Thanks for taking the time to explain all the details about Delphi/C++ VMTs – BigONotation Mar 23 '16 at 12:46