Using Delphi library with C++ Builder

| category: Programming | author: st
Tags: ,

Since early XE versions C++ Builder supports Delphi units directly added in C++ project and compiles them as well as C/C++ source. However, this approach has several drawbacks:

  • Usually, Delphi files are stored in separated folders of other applications and packages. So your C++ project will point to many relative or absolute paths beyond the project root folder
  • Delphi units may have many dependencies. Therefore, you should include them all into your C++ project as well

The solution is to group Delphi units into package library and then use it in C++ Builder project with a minimum of dependencies. The example below is tested with Delphi/C++ Builder Berlin 10.1 but should work with previous versions too.

Prepare project directories

I suggest the following folder structure for my test example.

MyProjects
├───Include
├───Libs
├───MyCBuilderApp
└───MyDelphiLib

MyCBuilderApp and MyDelphiLib are the subdirectories for corresponding projects. Folders Include and Libs will store shared generated headers and libraries.

Create Delphi package library

  • Start Delphi and create new Delphi project of "Package" type
  • Save project as MyDelphiLib.dproj into MyDelphiLib folder
  • Add new Delphi unit and save it as MyDelphiUnit.pas in the project folder
  • Change project settings and specify C++ generation options
  • Write unit code as in following example
  • Build project and verify that HPP and LIB files are generated

MyDelphiUnit contains the class THello that prints out the messages using an assigned event handler or a delegate class implementing the output. The implementation of the output method is injected into the class constructor so you can see a dependency injection pattern at the same time.

unit MyDelphiUnit;

interface

type
  TOnMessageEvent = procedure (const Msg: string) of object;

  TMessageDelegate = class(TObject)
  public
    procedure DoMessage(const AMsg: string); virtual; abstract;
  end;

  THello = class(TObject)
  private
    FOnMessage: TOnMessageEvent;
    FDelegate: TMessageDelegate;
  protected
    procedure DoMessage(const AMsg: string);
  public
    constructor Create(const AOnMessage: TOnMessageEvent); overload;
    constructor Create(const ADelegate: TMessageDelegate); overload;
    destructor Destroy; override;
    procedure PrintHello;
  end;

implementation

{ THello }

constructor THello.Create(const AOnMessage: TOnMessageEvent);
begin
  inherited Create;
  FOnMessage := AOnMessage;
  DoMessage('THello.Create');
end;

constructor THello.Create(const ADelegate: TMessageDelegate);
begin
  inherited Create;
  FDelegate := ADelegate;
  DoMessage('THello.Create');
end;

destructor THello.Destroy;
begin
  DoMessage('THello.Destroy');
  inherited Destroy;
end;

procedure THello.DoMessage(const AMsg: string);
begin
  if Assigned(FOnMessage) then
    FOnMessage(AMsg)
  else if Assigned(FDelegate) then
    FDelegate.DoMessage(AMsg);
end;

procedure THello.PrintHello;
begin
  DoMessage('Hello from MyDelphiLib!');
end;

end.

Create C++Builder application

  • Start C++ Builder and create new console application. When prompted by project wizard, specify VCL framework to use.
  • Save project as MyCBuilderApp.cbproj into MyCBuilderApp folder as well as main.cpp and header files
  • Change project options for "include" and "library" search paths
  • In the main file include MyDelphiUnit.hpp as well as corresponding library
  • Write some C++ code that uses Delphi class as in following example
  • Build project and run it
#include <vcl.h>
#include <windows.h>

#pragma hdrstop
#pragma argsused

#include <tchar.h>
#include <stdio.h>

#include <MyDelphiUnit.hpp> // Delphi-generated header
#pragma comment(lib, "MyDelphiLib.lib") // Delphi-generated library to link

using namespace std;

class MyDelegate : public TMessageDelegate
{
  public:
    virtual void __fastcall DoMessage(const UnicodeString Msg)
    {
      wcout << L"DC: " << Msg << endl;
    }
};

class HelloCall
{
  private:
    void __fastcall OnMessage(const UnicodeString Msg)
    {
      wcout << L"FP: " << Msg << endl;
    }

  public:
    void Hello()
    {
      THello *hello = new THello(&OnMessage);
      hello->PrintHello();
      delete hello;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
  // Function pointer (event) implementation
  HelloCall hc;
  hc.Hello();
  // Delegate class implementation
  MyDelegate *delegate = new MyDelegate();
  THello *hello = new THello(delegate);
  hello->PrintHello();
  delete hello;
  delete delegate;
  return 0;
}

If all sources were built with no errors, you should see the following output on Windows console.

Debugging Delphi package sources in C++Builder

You may need to step into Delphi package sources from C++ code sometimes. Open Delphi package project settings, select target debug configuration for all or specific platform and check following options.

  • Delphi compiler
    • Compiling
      • Optimization = "false"
      • Debug information = "Debug information"
      • Stack frames = "true"
      • Use debug DCU's = "true"
    • Linking
      • Debug information = "true"
      • Include remote debug symbols = "true"

Save settings and rebuild Delphi package.

In C++Builder project options:

  • go to Debugger/Source path;
  • add paths to Delphi sources.

Restart C++ Builder and recompile the project. From now you should be able to step into Delphi source code.

Conclusions

Using Delphi code in C++ Builder is pretty easy but you should structure your packages to share them with a minimum of dependencies. Adding Delphi files directly into C++ Builder project is an excusable solution only for standalone Delphi units with a small number of dependencies in the uses section that won't be shared for other projects later.