Using C++ objects in Delphi

Delphi is one of the greatest RAD tools on the market, but it in this currently C++-dominated world, it can sometimes be hard to find a Delphi or Pascal solution to your problem. There is, however, a chance you'll find a C++ class instead. This article describes several ways that enable you to use C++ classes from your Delphi code.

First the bad news: you won't be able to directly link the classes into your Delphi code. The Delphi linker can't link C++ object files into your application. You can link C object files, but that is the subject of another article. You'll need access to a C++ compiler that can create DLLs, and use the classes from the DLL.

to topDifferent object structures

The greatest difficulty in interfacing Delphi and C++ is, that their object structures differ. All Delphi classes descend from TObject, and are created on the heap. C++ classes are more like Delphi records with methods, and can be created statically or dynamically. They are similar to the legacy object types, carried over into Delphi from Borland and Turbo Pascal.

Fortunately there is not such a big difference internally. The only thing that distinguishes the memory layout of a Delphi class and a C++ class is, that the Delphi class always has a pointer to the Virtual Method Table (VMT), as its first field. C++ classes don't have a common ancestor like TObject, and don't always have virtual methods (or virtual member functions, as they are called in C++-speak), so the VMT field can be missing or at a different offset in the object. But you can force the C++ class to have one, at the same offset as TObject, by simply telling it to contain no data fields (members), and by making at least one method (member function) virtual.

to topExporting methods

Another problem is exporting methods from a DLL. There are basically two ways: the first one "flattens" the C++ class into a set of C functions, which all take an object as first parameter; the second one uses virtual, abstract methods.

Say you have the following simple C++ object: a Console class, that uses the functions in conio.h to achieve simple console functionality. On creation, it saves the current screen and, on destruction, restores it again.

enum TextColors
{
    tcBLACK,
    tcBLUE,
    tcGREEN,
    tcCYAN,
    tcRED,
    tcMAGENTA,
    tcBROWN,
    tcLIGHTGRAY,
    tcDARKGRAY,
    tcLIGHTBLUE,
    tcLIGHTGREEN,
    tcLIGHTCYAN,
    tcLIGHTRED,
    tcLIGHTMAGENTA,
    tcYELLOW,
    tcWHITE
};

class Console
{

private:
    text_info oldState;
    char *screenBuffer;
public:
    Console(void);
    virtual ~Console(void);
    void reset(void);
    void clearScreen(void);
    void gotoXY(int x, int y);
    void textColor(TextColors newColor);
    void textAttribute(int newAttribute);
    void textBackground(int newBackground);
    int readKey(void);
    bool keyPressed(void);
    void write(const char *text);
    void writeLn(const char *text);
};

This class has 10 methods, a destructor and a constructor. I will now demonstrate the two main ways of using this C++ class from Delphi.

to top"Flattening" the object

To "flatten" a class, you export a simple C function for each method, as well as functions for the constructor and the destructor. The first parameter of the functions (except for the "constructor" function) should be a pointer to the object. To flatten the Console class, you would declare 12 functions like this:

#include <windows.h>
#include "console.h"

typedef Console *ConsoleHandle;

// define a macro for the calling convention and export type
#define EXPORTCALL __declspec(dllexport) __stdcall

extern "C" 
{

    ConsoleHandle EXPORTCALL NewConsole(void)
    {
        return new Console();
    }
    
    void EXPORTCALL DeleteConsole(ConsoleHandle handle)
    {
        delete handle;
    }
    
    void EXPORTCALL ConsoleReset(ConsoleHandle handle)
    {
        handle->reset();
    }
    
    void EXPORTCALL ConsoleClearScreen(ConsoleHandle handle)
    {
        handle->clearScreen();
    }
    
    void EXPORTCALL ConsoleGotoXY(ConsoleHandle handle, 
        int x, int y)
    {
        handle->gotoXY(x, y);
    }
    
    void EXPORTCALL ConsoleTextColor(ConsoleHandle handle, 
        TextColors newColor)
    {
        handle->textColor(newColor);
    }
    
    void EXPORTCALL ConsoleTextAttribute(ConsoleHandle handle,
        int newAttribute)
    {
        handle->textAttribute(newAttribute);
    }
    
    void EXPORTCALL ConsoleTextBackground(ConsoleHandle handle,
        int newBackground)
    {
        handle->textBackground(newBackground);
    }
    
    int EXPORTCALL ConsoleReadKey(ConsoleHandle handle)
    {
        return handle->readKey();
    }
    
    bool EXPORTCALL ConsoleKeyPressed(ConsoleHandle handle)
    {
        return handle->keyPressed();
    }
     
    void EXPORTCALL ConsoleWrite(ConsoleHandle handle, 
        const char *text)
    {
        handle->write(text);
    }
    
    void EXPORTCALL ConsoleWriteLn(ConsoleHandle handle, 
        const char *text)
    {
        handle->writeLn(text);
    }
    
} // extern "C"

#pragma argsused
int WINAPI DllEntryPoint(HINSTANCE hinst, 
    unsigned long reason, void* lpReserved)
{
    return 1;
}

Now you only have to compile this to a DLL, and your object can be used from Delphi in the same manner as any API call. The interface unit would look like this:

unit ConsoleFlat;

interface

uses
  SysUtils;

type
  ConsoleHandle = Pointer; // no need to know the real type

  TTextColor = (
    tcBLACK, tcBLUE, tcGREEN, tcCYAN, tcRED, tcMAGENTA,
    tcBROWN, tcLIGHTGRAY, tcDARKGRAY, tcLIGHTBLUE, tcLIGHTGREEN, 
    tcLIGHTCYAN, tcLIGHTRED, tcLIGHTMAGENTA, tcYELLOW, tcWHITE
  );
  
function NewConsole: ConsoleHandle; stdcall; 
procedure DeleteConsole(handle: ConsoleHandle); stdcall;
procedure ConsoleReset(handle: ConsoleHandle); stdcall;

procedure ConsoleClearScreen(handle: ConsoleHandle); stdcall;
procedure ConsoleGotoXY(handle: ConsoleHandle; 
  x, y : Integer); stdcall;
procedure ConsoleTextColor(handle: ConsoleHandle; 
  newColor: TTextColor); stdcall;
procedure ConsoleTextAttribute(handle: ConsoleHandle;
  newAttribute: Integer); stdcall;

procedure ConsoleTextBackground(handle: ConsoleHandle; 
  newBackground: TTextColor); stdcall;
function ConsoleReadKey(handle: ConsoleHandle): Integer; stdcall;
function ConsoleKeyPressed(handle: ConsoleHandle): Boolean; stdcall;
procedure ConsoleWrite(handle: ConsoleHandle; text: PChar); stdcall;

procedure ConsoleWriteLn(handle: ConsoleHandle; text: PChar); stdcall;

implementation

const
  DLLName = 'ConsoleFlat.dll'; 
  
function NewConsole; external DLLName; 

procedure DeleteConsole; external DLLName;
procedure ConsoleReset; external DLLName;
procedure ConsoleClearScreen; external DLLName;

procedure ConsoleGotoXY; external DLLName;
procedure ConsoleTextColor; external DLLName;
procedure ConsoleTextAttribute; external DLLName;

procedure ConsoleTextBackground; external DLLName;
function ConsoleReadKey; external DLLName;
function ConsoleKeyPressed; external DLLName;

procedure ConsoleWrite; external DLLName;
procedure ConsoleWriteLn; external DLLName;

end.

This can then be used as you please. The disadvantage is, of course, that you are using functions, where the C++ user can use a class. So instead of

  Console := TConsole.Create;
  try
    Console.TextBackground(tcRED);
    Console.TextColor(tcYELLOW);
    Console.ClearScreen;
    Console.WriteLn('Yellow on red');
    Console.ReadKey;
  finally
    Console.Free;
  end;

you must do the following:

  Console := NewConsole;
  try
    ConsoleTextBackground(Console, tcRED);
    ConsoleTextColor(Console, tcYELLOW);
    ConsoleClearScreen(Console);
    ConsoleWriteLn(Console, 'Yellow on red');
    ConsoleReadKey(Console);
  finally
    DeleteConsole(Console);
  end;

This leads to the next way of using the class from Delphi, one which is a bit more convenient.

to topUsing pure virtual classes

Pure virtual classes are what the Delphi programmer would call pure abstract classes. These have the advantage that they only have virtual methods, and absolutely no data members. Pure Delphi abstract classes have a similar layout. The only member of both classes is therefore a pointer to the VMT. The VMT is an array, one for each class (fortunately, not for each object), that contains pointers to the actual implementation of each virtual method for that particular class. This is how polymorphism is implemented. Function calls are only coded as jumps to the function in the VMT of the class at a specific index. So different descendant classes, which have different VMTs, can implement a virtual function differently; the function pointer found at the same index in the VMT will always be used, but it will point to different functions in each class.

But now some of you may say: what about the virtual functions of TObject? These must probably already fill quite a few slots of the array. That is true, but fortunately, the VMT of Delphi objects is layed out in such a way, that these predefined virtual methods are all at a negative offset from the address to which the VMT pointer points. The first slot, at offset 0, contains the first user defined virtual method.

This fortunate circumstance makes it rather simple to pass the addresses of all methods (as long as they are virtual) in one step, as one simple pointer to the VMT. The only thing you must take care of is to assure that there is a pure abstract base class defined in both languages, and that the order of declaration of the methods is exactly the same. This is actually how interfaces were defined in versions of Delphi prior to Delphi 3.

Note that this way, you'll be using a C++ class as if it were a Delphi class. But it remains a C++ class, so it does not have the TObject methods or properties like InstanceSize or AfterConstruction. You should not try to call them! You should really use the class as a C++ class, or as some kind of lightweight interface, without the COM functions.

Only if you follow this advice, you will be able to use the C++ class safely.

There is one problem. The class you want to export is not a pure virtual class, it is a normal class, with a data member and non-virtual functions. There are two possiblities. If you have the source code, you can define your own abstract ancestor of the class, and then make your class a descendant of it:

                                                      
#define STDCALL __stdcall

class AbstractConsole
{
public:
    virtual void STDCALL reset(void) = 0;
    virtual void STDCALL clearScreen(void) = 0;
    virtual void STDCALL gotoXY(int x, int y) = 0;
    virtual void STDCALL textColor(TextColors newColor) = 0;
    virtual void STDCALL textAttribute(int newAttribute) = 0;
    virtual void STDCALL textBackground(int newBackground) = 0;
    virtual int STDCALL readKey(void) = 0;
    virtual bool STDCALL keyPressed(void) = 0;
    virtual void STDCALL write(const char *text) = 0;
    virtual void STDCALL writeLn(const char *text)= 0;
    virtual void STDCALL free(void) = 0;
};


#ifndef DLLCODE
#define EXTERN __declspec(dllimport) __stdcall
#else
#define EXTERN __declspec(dllexport) __stdcall
#endif

extern "C" AbstractConsole* EXTERN NewConsole(void);

NOTE: some compilers, like Microsoft Visual C++, don't always compile member functions as cdecl by default, but use their own, non-standard calling convention instead. So it is best to compile all member functions as stdcall.

Now you could simply change the first line of the declaration of your original Console class, and recompile.

class Console : public AbstractConsole
{ // etc...

There is no need to declare all the functions of Console virtual. In C++, in descendant classes, a member function with the same signature as a virtual member function as the ancestor class, will automatically override that function. So the member functions of our original class will automatically become virtual.

However, often it is not possible, or desired, to make all functions of a class virtual, or to change the ancestor. In that case you will have to use multiple inheritance or aggregation to use the functionality of Console.

#include "console.h"
#include "aconsole.h"

class ConcreteConsole : public AbstractConsole, private Console
{
    void reset(void);
    void clearScreen(void);
   
    // etc...

   
    void free(void);
};

And the functions simply call the Console functions to perform their tasks. Below is an excerpt of the implementing code.

#include "console.h"
#include "cconsole.h"

void ConcreteConsole::reset(void)
{
    Console::reset();
}

void ConcreteConsole::clearScreen(void)
{
    Console::clearScreen();
}

// etc...

void ConcreteConsole::free(void)
{
    if (this)
        delete this;
}

Of course there is still a need for an export function. If you chose to change the inheritance, i.e. decided to let the Console class inherit from AbstractConsole, it will look like this:

AbstractConsole* EXTERNCALL NewConsole()
{
    return new Console();
}

If, on the other hand, you decided to use aggregation and a wrapper, it would look like:

AbstractConsole* EXTERNCALL NewConsole()
{
    return new ConcreteConsole();
}

The Delphi class doesn't have to know about AbstractConsole or ConcreteConsole, and can simply be called TConsole, or whatever you like. The import unit will look like this:

unit ConsoleDLL;

interface

type
  TTextColor = (
    tcBLACK, tcBLUE, tcGREEN, tcCYAN, tcRED, tcMAGENTA,
    tcBROWN, tcLIGHTGRAY, tcDARKGRAY, tcLIGHTBLUE, tcLIGHTGREEN,
    tcLIGHTCYAN, tcLIGHTRED, tcLIGHTMAGENTA, tcYELLOW, tcWHITE
  );

  TConsole = class

    procedure Reset; virtual; cdecl; abstract;
    procedure ClearScreen; virtual; cdecl; abstract;
    procedure GotoXY(x, y : Integer); virtual; cdecl; abstract;
    procedure TextColor(newColor: TTextColor); virtual; cdecl; abstract;
    procedure TextAttribute(newAttribute: Integer);
      virtual; cdecl; abstract;
    procedure TextBackground(newBackground: TTextColor);
      virtual; cdecl; abstract;
    function ReadKey: Integer; virtual; cdecl; abstract;
    function KeyPressed: Boolean; virtual; cdecl; abstract;
    procedure Write(text: PChar); virtual; cdecl; abstract;
    procedure WriteLn(text: PChar); virtual; cdecl; abstract;
    procedure Free; virtual; cdecl; abstract;
  end;


function NewConsole: TConsole; stdcall;

implementation

function NewConsole; external 'ConsoleDLL.dll';

end.

This is all you need to use the C++ Console class from the DLL. It can be used in a similar fashion as I described above:

  Console := NewConsole;
  try
    Console.TextBackground(tcRED);
    Console.TextColor(tcYELLOW);
    Console.ClearScreen;
    Console.WriteLn('Yellow on red');
    Console.ReadKey;
  finally
    Console.Free;
  end;

to 
topConclusion

Although it is often said that C++ classes can't be used in Delphi, this is only partly true, as this article demonstrates. But which of the two ways of importing C++ classes is preferrable?

The second way, using virtual classes, is of course much more convenient to use. It is almost as if your class was written in Delphi. But you can not inherit from the class and add your own functionality, since you can't call the inherited constructor. You can't call any of the TObject functions either, or call code that relies on them. NewConsole will always return a ConcreteConsole (or a Console, if you changed the inheritance). Also, it is a bit more work on the C++ side.

The "flat" variety is less convenient, but has one level of indirection less (since calling virtual functions is also an extra level of indirection), and that makes it a bit faster.

Of course, with both approaches, you could write a Delphi class that wraps either the abstract class, or the functions. But that would introduce yet another level of indirection.

The C++ source code for both DLLs and small demo programs in Delphi can be downloaded from the Downloads page.

Rudy Velthuis

Standard Disclaimer for External Links

These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval of any of the products, services or opinions of the corporation or organization or individual. I bear no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Disclaimer and Copyright

The coding examples presented here are for illustration purposes only. The author takes no responsibility for end-user use. All content herein is copyrighted by Rudy Velthuis, 2005, 2007, and may not be reproduced in any form without the author's permission. Source code written by Rudy Velthuis presented as download is subject to the license in the files.