Using C++ objects in Delphi
Rudy Velthuis
Rudy Velthuis
[SHOWTOGROUPS=4,20]
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.
Different 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.
Even if the internal structure can be made similar, it is not safe to model the structure, especially since C++ classes can be the result of multiple inheritance and can have private data members that are hard to mimic or find out. Actually, mimicking the structure of C++ abstract classes was my first approach, but you would never be able to construct or Free such a class. They would not have the RTTI and special methods of Delphi classes either.
Exporting 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 that are laid out like in a COM-style interface.
Demo class
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.
I know it is not a very useful class, since a C++Builder user would simply use conio.h directly, but it nicely demonstrates how you can use an existing C++ class and it is easy to implement as a demo class. And some of the functionality is not found in the conio.h of other compilers.
This class has ten instance methods, a destructor and a constructor. I will now demonstrate two ways of using this C++ class from Delphi.
“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:
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:
This can then be used anyway you need. The disadvantage is, of course, that you are using functions, where the C++ user can use a class. So instead of
you must do the following:
This leads to the next way of using the class from Delphi, one which is a bit more convenient.
[/SHOWTOGROUPS]
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.
Different 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.
Even if the internal structure can be made similar, it is not safe to model the structure, especially since C++ classes can be the result of multiple inheritance and can have private data members that are hard to mimic or find out. Actually, mimicking the structure of C++ abstract classes was my first approach, but you would never be able to construct or Free such a class. They would not have the RTTI and special methods of Delphi classes either.
Exporting 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 that are laid out like in a COM-style interface.
Demo class
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.
I know it is not a very useful class, since a C++Builder user would simply use conio.h directly, but it nicely demonstrates how you can use an existing C++ class and it is easy to implement as a demo class. And some of the functionality is not found in the conio.h of other compilers.
enum TextColors { tcBLACK, tcBLUE, tcGREEN, tcCYAN, tcRED, tcMAGENTA, tcBROWN, tcLIGHTGRAY, tcDARKGRAY, tcLIGHTBLUE, tcLIGHTGREEN, tcLIGHTCYAN, tcLIGHTRED, tcLIGHTMAGENTA, tcYELLOW, tcWHITE }; class Console { public: Console(); virtual ~Console(); void reset(); void clearScreen(); void gotoXY(int x, int y); void textColor(TextColors newColor); void textAttribute(int newAttribute); void textBackground(int newBackground); int readKey(); bool keyPressed(); void write(const char *text); void writeLn(const char *text); private: text_info oldState; char *screenBuffer; }; |
This class has ten instance methods, a destructor and a constructor. I will now demonstrate two ways of using this C++ class from Delphi.
“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 // Can't use the class directly, so it is treated as an opaque handle. // THandle is guaranteed to have the right size, even on other platforms. ConsoleHandle = THandle; 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: PAnsiChar); stdcall; procedure ConsoleWriteLn(handle: ConsoleHandle; text: PAnsiChar); 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 anyway you need. 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.
[/SHOWTOGROUPS]