Introduction

C++ is often associated with systems programming, games, high-performance applications and other specialised domains. C++Builder brings something rather different to the language: the rapid application development experience familiar to Delphi developers.

At the time of writing, RAD Studio 13.2 is approaching release, continuing Embarcadero's recent investment in C++Builder and the wider development environment. C++Builder's Modern 64-bit toolchain is based on Clang 20 and supports C++23, while the upcoming release continues to improve developer productivity and tooling across RAD Studio.

C++Builder combines C++ with the RAD Studio IDE, the Visual Component Library (VCL), and Run-Time Library (RTL) shared with Delphi. This makes it possible to build native Windows applications using visual designers, reusable components and a rich application framework, while retaining the power of C++.

For an experienced C++ developer, the initial learning curve comes from understanding the Delphi-derived ecosystem surrounding it: the RTL and VCL, language extensions, special Delphi types, calling conventions, packages and interoperability with Delphi code.

Consequently, one of the best ways to learn more about C++Builder is to become familiar with Delphi material. Much of the documentation, books and other material describing the RTL, VCL and component model applies directly to C++Builder. I've collected some useful Delphi resources here.

This article provides a quick overview of the essentials you need to get up and running with C++Builder. For more comprehensive documentation, see Embarcadero's C++Builder documentation.


The Delphi Object Model

The RTL and VCL are written in Delphi and use the Delphi object model. C++Builder provides language extensions and type mappings that allow C++ classes to participate directly in that model.

Delphi uses single class inheritance, along with multiple interface inheritance. TObject is the root of the class hierarchy. We can inherit from any eligible Delphi class in this hierarchy:

class TPerson : public System::TObject {
    // ...
};

A C++ class deriving from TObject, directly or indirectly, is therefore a Delphi-style class and can participate in Delphi's RTTI and other object and component services. A Delphi-style C++ class is restricted to a single concrete class inheritance path, but can additionally implement Delphi interfaces.

Base TypesAllowed?Example
One Delphi class Yes class TMyClass : public TBase
Two Delphi classes No class TMyClass : public TBase1, public TBase2
Delphi class + ordinary C++ class No class TMyClass : public TBase, public MyCppBase
Delphi class + Delphi interface Yes class TMyClass : public TBase, public IMyInterface
Delphi class + multiple Delphi interfaces Yes class TMyClass : public TBase, public IFoo, public IBar

Class References and TClass

One important difference from standard C++ is Delphi's concept of a class reference, sometimes called a metaclass. A class reference represents the class itself rather than an instance of that class.

The base class-reference type is TClass, defined in Delphi as:

TClass = class of TObject;

A TClass value can therefore refer to TObject or one of its descendant classes.

C++Builder exposes Delphi class references using TMetaClass*, and the __classid extension can be used to obtain the class reference for a particular class.

This is more than type metadata. Delphi class methods can be virtual, allowing polymorphic behaviour at the class level. A virtual class method can therefore dispatch according to the class represented by a class reference, rather than according to an object instance.

C++Builder represents Delphi class methods using the __classmethod extension:

__classmethod virtual String __fastcall GetClassName();

Static Delphi class methods, by contrast, map to ordinary C++ static methods.

TMyClass = class
public
  // Statically-bound instance method (Self)
  procedure TakeOrder;

  // Virtual instance method (Self)
  procedure MakeLunch; virtual;

  // Statically-bound class method (Self)
  class procedure Greet;

  // Virtual class method (Self)
  class procedure Introduce; virtual;

  // Static class method (no Self)
  class procedure Farewell; static;
end;

Like instance methods, ordinary class methods receive an implicit Self parameter. For an instance method, Self refers to the object instance; for a class method, it refers to the class itself through its class reference (metaclass). A static class method has no Self parameter.

Delphi declarationSelfDispatchC++Builder equivalent
procedure TakeOrder; Object instance Static void __fastcall TakeOrder();
procedure MakeLunch; virtual; Object instance Virtual virtual void __fastcall MakeLunch();
class procedure Greet; Class reference Static __classmethod void __fastcall Greet();
class procedure Introduce; virtual; Class reference Virtual __classmethod virtual void __fastcall Introduce();
class procedure Farewell; static; None Static static void __fastcall Farewell();

Construction

Construction of Delphi-style classes requires some care because C++Builder combines aspects of the C++ and Delphi object models

A Delphi constructor is exposed to C++ as a C++ constructor. For example, Delphi's virtual TComponent.Create constructor appears in C++ as:

__fastcall virtual TComponent(TComponent* AOwner);

When deriving from a Delphi class, the inherited constructor is called using the C++ initializer-list syntax:

class TMyComponent : public System::Classes::TComponent {
public:
    __fastcall TMyComponent(System::Classes::TComponent* Owner) 
        : TComponent(Owner) {
        // ...
    }
};

There is, however, an important difference from ordinary C++ construction. A Delphi-style object has the runtime type of the most-derived class throughout construction. If a Delphi base-class constructor calls a virtual method that has been overridden by the C++ derived class, the derived override can therefore be called before the derived class's member initialization or constructor body has executed.

For example, if we have TBase defined in Delphi:

TBase = class(TObject)
public
  constructor Create; virtual;
  procedure Initialise; virtual;
end;

constructor TBase.Create;
begin
  inherited;
  Initialise;
end;

procedure TBase.Initialise;
begin
  // ...
end;

And we inherit from it in C++Builder:

class TMyClass : public TBase {
private:
    int fValue;

public:
    __fastcall TMyClass () : fValue(42) {
        // ...
    }

    void __fastcall Initialise() override {
        // fValue has not yet been initialized to 42
        // when called from TBase's constructor.
    }
};

This differs from standard C++, where virtual dispatch during construction is restricted to the class whose constructor is currently executing.

In a Delphi-style hierarchy, an override should not assume that members initialized by the derived constructor are available when it is called from a base-class constructor.

In the example above, construction of TMyClass causes C++Builder to invoke TBase's default constructor. TBase.Create then calls inherited, which invokes its base constructor:

TObject
   ↓
TBase
   ↓
TMyClass

Delphi construction does not generally mean that the base constructor executes before the derived constructor. The selected Delphi constructor begins executing first and explicitly invokes an ancestor constructor using inherited. In our example, TBase.Create calls its inherited constructor before continuing with its own initialization. A particular ancestor constructor can be selected explicitly, such as: inherited Create(obj);.

Back to the example. When the TBase constructor is executing, it calls:

TBase.Create
     |
     +--> Initialise
              |
              +--> TMyClass::Initialise()

because the Delphi-style object already has TMyClass as its most-derived runtime type.

But TMyClass's C++ initialization hasn't happened yet. In particular:

fValue(42)

has not executed. So TMyClass::Initialise() is running against a derived object whose C++ state is not yet initialized as that method expects. That's the danger.

In ordinary standard C++, the same virtual call from TBase's constructor would resolve to TBase::Initialise(), specifically to prevent dispatch into a derived class that hasn't been constructed yet.

In general, avoid calling or relying upon virtual methods from constructors.


Destruction

Destruction has a corresponding interoperability requirement: a destructor declared in a Delphi-style C++ class must be virtual. C++Builder enforces this requirement.

class TMyClass : public System::TObject {
public:
    __fastcall TMyClass();

    virtual __fastcall ~TMyClass();
};

Delphi-style classes use Delphi-compatible constructor and destructor behaviour, including lifecycle hooks such as AfterConstruction and BeforeDestruction, together with object cleanup machinery inherited from TObject.

Destruction also retains the object's most-derived runtime type while base-class destructors execute. Consequently, virtual methods called from Delphi base-class destructors can dispatch to overrides belonging to an already-destroyed derived portion of the object. Virtual calls from constructors and destructors therefore deserve particular care in mixed Delphi/C++ class hierarchies.

Inheritance:

TObject  <-  TBase  <-  TMyClass

Construction:

TObject  ->  TBase  ->  TMyClass

Destruction:

TObject  <-  TBase  <-  TMyClass

Properties

Delphi properties are represented in C++Builder using the __property extension. A property looks like a field when used, but its read and write operations can be backed by fields or methods.

For example:

class TPerson : public System::TObject {
private:
    // backing field for the Name property
    String fName;

    // getter method for the Name property
    String __fastcall GetName(); 

    // setter method for the Name property
    void __fastcall SetName(const String value);
    
public:
    __property String Name = { read=GetName, write=SetName };
};

The property can then be used with ordinary field-like syntax:

person->Name = L"David";

String name = person->Name;

Getter and setter methods are optional in most cases. We could simply do:

__property String Name = { read=fName, write=fName };

Indexed properties are an exception: their read and write specifiers must refer to accessor methods:

class TPeople : public System::TObject {
private:
    String __fastcall GetName(int index);
    void __fastcall SetName(int index, const String value);

public:
    __property String Names[int index] = { read=GetName, write=SetName };
};

The property can then be accessed using array-like syntax:

people->Names[0] = L"David";

String name = people->Names[0];

Indexed properties are not limited to integer indexes and can also have multiple indexes. The index parameters are passed to the property's accessor methods. Delphi default indexed properties are exposed to C++ through operator[], and we can use the same operator to implement equivalent behaviour in C++Builder:

String __fastcall operator[](int index) {
    return Names[index];
}

Visibility

Delphi visibility differs slightly from standard C++, particularly because a Delphi unit forms an additional visibility boundary. The main visibility levels encountered when consuming Delphi classes from C++Builder are:

Delphi visibility Accessibility
strict private Only within the declaring class
private Within the unit containing the declaring class
strict protected Within the declaring class and its descendants
protected Within the declaring unit or descendants declared elsewhere
public Wherever the class is accessible
published Public visibility plus Delphi RTTI and component infrastructure

C++Builder represents Delphi published visibility using __published:

class TMyComponent : public TComponent
{
private:
    String fCaption;

__published:
    __property String Caption = { read=fCaption, write=SetCaption };
};

From a C++ accessibility perspective, __published members have public visibility, but Delphi-style RTTI is generated for them. Published properties can consequently participate in component streaming and, for components installed in the IDE, be exposed through the Object Inspector.


Parameter Passing

Delphi parameter declarations do not always map directly to ordinary C++ parameter passing.

When calling Delphi code from C++Builder, the parameter modifiers used by the Delphi API determine how arguments are exposed to C++.

Delphi declaration C++Builder significance
Value parameter Passed by value in the normal case
var T Modifiable reference
const T Read-only parameter; exact C++ representation depends on the type and ABI
out T Output parameter with Delphi initialization and lifetime semantics
Untyped var / const Untyped parameter exposed through C++ pointer/reference machinery
array of T Delphi open array, exposed using C++Builder's open-array support
array of const Delphi variant open array represented using TVarRec

Of particular interest to C++ developers are Delphi open arrays. These are represented at the binary interface by an array together with its highest valid index, rather than its element count. C++Builder provides macros for constructing the corresponding arguments when calling Delphi APIs.

SomeDelphiFunction(OPENARRAY(int, (10, 20, 30)));

OPENARRAY constructs an open-array argument from the supplied values. C++Builder also provides EXISTINGARRAY and SLICE for passing existing arrays, while ARRAYOFCONST constructs the TVarRec array required by Delphi array of const parameters.

Format(L"%s is %d", ARRAYOFCONST((L"David", 55)));

The practical rule is to follow the declaration exposed in the generated C++ header rather than assuming that a Delphi parameter modifier has a direct equivalent in standard C++. This is particularly important for managed types, open arrays and output parameters, where Delphi lifetime and ABI semantics form part of the interface.


Calling Conventions

Methods exposed through Delphi classes may contain calling-convention annotations such as:

void __fastcall SetName(const String value);

Calling conventions form part of the binary interface used when invoking a function, determining details such as how parameters and return values are passed. C++Builder supports the calling conventions used by Delphi and Windows so that code can interoperate across these boundaries.

Annotation Significance
__fastcall Delphi-compatible register calling convention, commonly encountered on Delphi methods
__cdecl C calling convention
__stdcall Standard Win32 calling convention, commonly encountered in Windows interoperability code
__pascal Pascal calling convention, primarily encountered in legacy code

These annotations form part of the interoperability contract and should not simply be removed because they appear unnecessary from the C++ source. In particular, C++Builder requires access methods for properties declared in a __published section to use __fastcall.

Historically, these distinctions mattered directly at the machine level. On 32-bit Windows, calling conventions could determine whether arguments were passed through registers or the stack, who was responsible for cleaning up the stack, and how symbols were decorated.

64-bit Windows simplified this considerably by standardising on the Microsoft x64 calling convention. The first four integer or pointer arguments are normally passed in RCX, RDX, R8 and R9, with additional arguments passed on the stack. Consequently, distinctions between conventions such as __cdecl, __stdcall and __fastcall largely disappear at the ABI level when targeting Win64. The annotations can nevertheless remain significant to C++Builder source compatibility and Delphi interoperability.

Conceptually:

Win32

__cdecl     ──> distinct convention
__stdcall   ──> distinct convention
__fastcall  ──> distinct convention


Win64

__cdecl     ──┐
__stdcall   ──┼──> Microsoft x64 ABI
__fastcall  ──┘

Special Delphi Types

C++Builder provides C++ representations of Delphi types that have no direct equivalent in standard C++. You'll encounter these types throughout the RTL, VCL and APIs originally written in Delphi.

Managed types have compiler/runtime-supported lifetime semantics, so operations such as initialization, assignment and destruction can involve more than simply copying their underlying representation.

String

One of the most commonly encountered Delphi managed types is its string type. Alongside std::string and std::wstring, C++Builder applications commonly use Delphi's managed UTF-16 string:

UnicodeString message = L"Hello from C++Builder";

which is also available as String:

String message = L"Hello from C++Builder";

DynamicArray

A Delphi dynamic array is exposed through the DynamicArray<T> template:

DynamicArray<int> values;
values.Length = 3;

values[0] = 10;
values[1] = 20;
values[2] = 30;

DynamicArray<T> is specifically designed to be binary-compatible with Delphi dynamic arrays. It should therefore be treated as a Delphi interoperability type rather than as an alternative to std::vector.

For your own C++ implementation code, prefer standard C++ containers unless you need to interact with a Delphi API or framework type.

Set

Delphi's set of types are similarly represented by the Set template:

Set<char, 'A' , 'Z'> letters;
        
letters << 'A' << 'B' << 'C' ; 

if (letters.Contains('B')) { 
    // ... 
}

Set<T, min, max> exists specifically to represent Delphi-compatible set types.

Because the complete Set type can be verbose, it is common to introduce a typedef for a particular set:

typedef Set<char, 'A', 'Z'> TCapitals;

TCapitals capitals;
capitals << 'A' << 'B' << 'C';

This also mirrors a pattern you'll frequently encounter in generated C++Builder headers, where Delphi types are exposed through convenient C++ typedefs.

Common Set operations include:

// Add an element
capitals << 'D';

// Test for an element
bool containsA = capitals.Contains('A');

// Remove an element
capitals >> 'B';

// Union
TCapitals combined = capitals + other;

// Difference
TCapitals difference = capitals - other;

// Intersection
TCapitals common = capitals * other;

Record

Delphi records are exposed to C++ as struct-like value types. A simple Delphi record therefore maps naturally to a C++ structure containing the corresponding fields. Records should not, however, automatically be treated as trivial C-style structures: their fields can themselves have Delphi-managed semantics.

TPerson = record
    Name: String;
    Age: Integer;
end;

For example, the Name field above is a managed Delphi string, so the record's lifetime involves more than simply copying its underlying bytes.

In generated C++ headers, records are represented as C++ structures marked with __declspec(delphirecord).

C++ code normally uses this generated type directly when passing records to or receiving them from Delphi APIs; no special annotation is required at the call site. Among other interoperability details, this allows C++Builder to honour Delphi's convention for records returned by value, including zero-initializing the return storage expected by Delphi.

Modern Delphi also supports custom managed records with user-defined initialization, finalization and assignment operators. These are not supported for C++ interoperability, so records intended to cross the Delphi/C++ boundary should avoid those features.

The mental model is:

// Delphi declared record
TPerson = record
    // ...
end;

Then:

// Compiler generates the following in a .hpp file        
struct __declspec(delphirecord) TPerson {
    // ...
};

// Include the .hpp and use the generated type normally
TPerson person;
ProcessPerson(person);

StaticArray

Delphi static arrays can appear in C++ as StaticArray<T, N>:

StaticArray<int, 4> values;
        
values[0] = 10;
values[1] = 20;

StaticArray provides a C++ representation compatible with Delphi static arrays.

This is particularly useful at Delphi/C++ boundaries, including cases where Delphi returns a static array by value, which is something a built-in C++ array cannot do.

Interface

Interfaces require a little more care. Delphi interfaces normally have reference-counting semantics, and C++Builder provides the DelphiInterface<T> template for managing them from C++. Its constructors, assignment operator and destructor manage the corresponding interface reference count.

You'll often see generated interface types with names such as:

_di_IMyInterface service;

rather than manipulating a raw IMyInterface*. The _di_ type is a DelphiInterface<T> wrapper generated for the corresponding Delphi interface and should normally be preferred when holding an interface reference.

C++Builder can also implement an interface declared in Delphi.

Consider the following Delphi interface:

ICar = interface
    ['{4A81B8E6-63A5-4E61-AD42-93F3C4D7B196}']
    function GetSpeed: Integer;
    procedure Accelerate;

    property Speed: Integer read GetSpeed;
end;

The generated C++ header exposes the interface and provides an _di_ICar wrapper based on DelphiInterface<ICar>. A C++Builder class can implement the interface using TCppInterfacedObject<ICar>:

class TSportsCar : public TCppInterfacedObject<ICar> {
private:
    int fSpeed = 0;

public:
    int __fastcall GetSpeed() {
        return fSpeed;
    }

    void __fastcall Accelerate() {
        fSpeed += 20;
    }

    __property int Speed = { read=GetSpeed };
};

TCppInterfacedObject<T> supplies the underlying interface support, including QueryInterface, AddRef and Release, leaving TSportsCar to implement the members specific to ICar.

The implementation can then be held through the generated Delphi interface wrapper:

_di_ICar car(static_cast<ICar*>(new TSportsCar()));

car->Accelerate();
car->Accelerate();

int speed = car->Speed;    // 40

The same interface can cross back into Delphi. For example, suppose a Delphi class exposes:

procedure TGarage.Add(Car: ICar);
begin
    // ...
end;

C++Builder uses the generated _di_ICar type when calling the Delphi method, so the C++ implementation can be passed through the interface:

_di_ICar car(static_cast<ICar*>(new TSportsCar()));

garage->Add(car);

Conceptually, this is what is happening:

Delphi ICar declaration
        ↓
generated C++ interface
        ↓
TSportsCar implements ICar
        ↓
_di_ICar manages the interface reference
        ↓
Delphi TGarage.Add(ICar)

This illustrates an important interoperability property: the interface can be declared in Delphi, implemented in C++, held using C++Builder's managed interface wrapper, and passed back to Delphi code without the caller needing to know the concrete implementation type.

Interface references and object references should not be mixed casually. A reference-counted interface controls the lifetime of its implementing object through AddRef and Release. When the final managed interface reference is released, the implementing object is destroyed. A separate raw TSportsCar* does not participate in that ownership and can consequently become a dangling pointer.

TSportsCar* sportsCar = new TSportsCar();

_di_ICar car(static_cast<ICar*>(sportsCar));

// Avoid treating sportsCar as an independently owned object.
// Its lifetime is now governed by the interface references.

When using a reference-counted implementation such as TCppInterfacedObject<T>, prefer to manage and pass the object through its interface references rather than retaining independently owned raw object pointers.

Common Delphi Type Mappings

When reading Delphi source or documentation, you'll also encounter Delphi's names for fundamental and commonly used types. Some useful mappings to their C++Builder representations are:

Delphi C++Builder / C++ Notes
Byte unsigned char 8-bit unsigned integer
ShortInt signed char 8-bit signed integer
Word unsigned short 16-bit unsigned integer
SmallInt short 16-bit signed integer
Cardinal unsigned int 32-bit unsigned integer
LongWord unsigned 32-bit unsigned integer on Windows; platform-dependent on 64-bit POSIX targets
Integer int 32-bit signed integer
Int64 __int64 64-bit signed integer
UInt64 unsigned __int64 64-bit unsigned integer
Single float 32-bit floating-point value
Double double 64-bit floating-point value
Extended long double Extended floating-point type; precision and representation depend on the target platform
Boolean bool Delphi Boolean type
Char wchar_t 16-bit Unicode character on Windows
AnsiChar char 8-bit character
PChar wchar_t* Pointer to Delphi Char on Windows
PWideChar wchar_t* Pointer to WideChar on Windows
Pointer void* Untyped pointer
Variant System::Variant Delphi variant value whose contained type can vary at run time
OleVariant System::OleVariant Variant restricted to OLE Automation-compatible types

These mappings are particularly useful when reading Delphi documentation and source code. As with the special Delphi types discussed above, however, the generated C++ header remains the authoritative representation to use when calling a Delphi API.


The RTL

The Run-Time Library (RTL) provides much of the underlying functionality used by both Delphi and C++Builder applications. This includes strings, streams, collections, filesystem functionality, exceptions and numerous utility classes and functions. Some of the more common RTL units include:

Unit Purpose
System Fundamental RTL types and low-level functionality
System.Classes Classes, streams, components and related infrastructure
System.SysUtils Exceptions, conversions, formatting, file/path and general utilities
System.StrUtils String manipulation and searching
System.IOUtils Modern filesystem/path/file/directory utilities
System.Math Mathematical functions, constants and related types
System.DateUtils Date and time calculations and utilities

RTL units are written in Delphi and exposed to C++Builder through generated C++ header files. To use an RTL unit from C++, include its corresponding .hpp header.

This is distinct from the standard C++ runtime library: C++Builder applications can use both the Delphi RTL and the standard C++ library.

RTL classes can be created and used directly from C++. For example, TStringList, declared in System.Classes, provides a simple collection of strings:

// include the unit with TStringList
#include <System.Classes.hpp>

// use the TStringList
auto strings = new System::Classes::TStringList();

strings->Add(L"Delphi");
strings->Add(L"C++Builder");
strings->Add(L"C++");

for (int i = 0; i < strings->Count; ++i) {
    auto value = strings->Strings[i];
    // ...
}

delete strings;

A useful correspondence to remember when working with Delphi units from C++ is:

Artifact Name Description
Delphi unit System.Classes The RTL unit containing the declarations
C++ header System.Classes.hpp The generated header included by C++
C++ namespace System::Classes The corresponding C++ namespace used to qualify its declarations

In Delphi, each unit name defines a scope, corresponding to a C++ namespace.


The VCL

The Visual Component Library (VCL) is the native Windows application framework at the heart of traditional Delphi and C++Builder development.

It builds upon the RTL, providing forms, buttons, edit controls, grids, menus, dialogs and a large hierarchy of non-visual components covering everything from database access to application services.

Some of the most important classes in the VCL object hierarchy are:

Class Role
TObject Root of the Delphi class hierarchy
TPersistent Base for objects supporting assignment and RTTI-based persistence
TComponent Adds ownership, naming, streaming and component infrastructure
TControl Base for visual VCL controls
TWinControl Base for windowed controls that can receive focus and contain other controls
TForm Represents an application window
TFrame Reusable visual container for composing user interfaces

A simplified view of the hierarchy is:

TObject
   ↓
TPersistent
   ↓
TComponent
   ↓
TControl
   ↓
TWinControl
   ├── TForm
   └── TFrame

TForm and TFrame have intermediate base classes that are omitted here; the diagram shows their position in the main VCL hierarchy rather than every inheritance level.

Object References and Ownership

Delphi class variables hold references to objects. Consequently, a Delphi object reference is normally exposed to C++ as a pointer. For example:

Button: TButton;

corresponds conceptually to:

TButton* Button;

This is why VCL code commonly uses familiar C++ pointer syntax:

Button1->Caption = L"Go";
MainForm->Show();

These pointers should not automatically be interpreted using ordinary C++ ownership assumptions. The VCL has its own component ownership model. A TComponent can own other components, and when the owner is destroyed it destroys the components that it owns.

auto button = new TButton(this);

Here this is passed as the component's Owner. The owner will consequently destroy the button as part of its own destruction.

Ownership should also not be confused with visual containment. VCL controls have a Parent that determines where they appear in the visual hierarchy, while Owner determines component lifetime management. The two are often related, but they represent different relationships.

auto button = new TButton(this);  // this owns the button
button->Parent = this;            // this visually contains the button

The first relationship governs lifetime; the second governs visual containment.

Events

The VCL event model is another important part of the Delphi component architecture. One of the most commonly encountered event types is TNotifyEvent, declared in Delphi as:

TNotifyEvent = procedure(Sender: TObject) of object;

The of object declaration makes this a method reference: it identifies both a method and the object instance on which that method should be invoked.

C++Builder represents this using its __closure language extension:

typedef void __fastcall (__closure *TNotifyEvent)(System::TObject* Sender);

A closure therefore carries the information necessary to invoke a particular member function on a particular object instance. VCL events are exposed as properties whose types are closures. For example, a button's OnClick event is a TNotifyEvent.

A typical C++Builder form can consequently contain a button and its event handler:

class TMainForm : public TForm {
__published:
    TButton* Button1;

    void __fastcall Button1Click(TObject* Sender);

public:
    __fastcall TMainForm(TComponent* Owner);
};

When the handler is connected through the Form Designer, RAD Studio stores the event association as part of the designed form. An event can also be assigned directly in C++:

Button1->OnClick = Button1Click;

When Button1 is clicked, the closure invokes TMainForm::Button1Click on the corresponding TMainForm instance. The Sender argument identifies the object that generated the event.

C++Builder classes can expose events of their own using the same mechanism. For example:

class TMyComponent : public TComponent {
private:
    TNotifyEvent fOnChanged;

public:
    __fastcall TMyComponent(TComponent* Owner)
        : TComponent(Owner) {
    }

__published:
    __property TNotifyEvent OnChanged = {
        read=fOnChanged,
        write=fOnChanged
    };
};

The component can invoke the event when appropriate:

if (fOnChanged) {
    fOnChanged(this);
}

This relationship between __property, __published and __closure is fundamental to the VCL component model: properties expose component state and behaviour, published RTTI makes eligible properties and events available to the design-time infrastructure, and closures allow object methods to serve as event handlers.


Packages

C++Builder and Delphi also have the concept of packages.

Packages group compiled code and components into reusable modules. You'll encounter them particularly when installing third-party component libraries or creating reusable components of your own.

A package is compiled into a BPL (Borland Package Library). A BPL is a Windows DLL with additional package-specific structure and metadata that allows it to participate in the Delphi/C++Builder package system.

Packages broadly serve two roles:

Package Purpose
Runtime Contains code and components used by an application while it is running
Design-time Integrates components, editors and other tooling into the RAD Studio IDE

Packages can depend on other packages.

Rather than statically linking all framework and component code into an executable, an application can be built to use runtime packages, allowing that code to reside in shared BPL modules.

Conceptually:

Application.exe
      │
      ├── rtl*.bpl
      ├── vcl*.bpl
      └── MyComponents.bpl

The exact BPL filenames vary by product and version, but the principle is that the application can load framework and component code from runtime packages rather than containing all of that code within the executable itself.

This distinction is particularly visible with third-party component libraries. Their runtime packages contain the code required by applications using the components, while their design-time packages register those components and associated designers or editors with RAD Studio. Design-time packages are therefore development dependencies and are not normally deployed with the application.


Common File Types

C++Builder projects contain a number of file types inherited from or shared with the Delphi and RAD Studio ecosystem. Some of the most commonly encountered are:

Extension Purpose
.cpp C++ implementation or project source file
.h / .hpp C++ header files; .hpp is also used for C++ headers generated from Delphi units
.cbproj C++Builder project file containing project configuration and build settings
.dfm VCL form or frame resource containing designed components, properties and relationships
.fmx FireMonkey form resource
.pas Delphi source unit
.dcu Compiled Delphi unit
.obj / .o Compiled C++ object file; .obj is used for Win32 and .o for other supported targets
.lib Static library or, on Windows, import library
.bpl Compiled package shared library
.bpi Package import library used when linking C++ code against a package
.dcp Delphi compiled package file containing package information used when building packages
.res Compiled resource file
.rc Windows resource script source file
.deployproj Deployment Manager configuration for the project

Of these, .dfm is particularly important when working with the VCL. It stores the designed state of a form or frame, including its components, property values and relationships, and is maintained by RAD Studio alongside the associated C++ source and header files.

The project's main .cpp file is also significant. It contains the application entry point and project-level initialization generated and maintained by C++Builder.


C++Builder Language Extensions

This table contains some of the more common C++Builder language extensions you'll encounter when working with Delphi, the RTL and the VCL.

Extension Purpose
__property Declares a Delphi-style property, providing field-like access that can be backed by fields or accessor methods. Properties are fundamental to the VCL component model and Object Inspector.
__published Adds Delphi-style published visibility. Suitable members receive Delphi RTTI and can participate in the VCL design-time component infrastructure.
__closure Declares an object-oriented method pointer. VCL events use closures so that, for example, a method on a form instance can handle a button's OnClick event.
__classmethod Declares a Delphi-compatible class method, which receives an implicit class reference and can participate in virtual dispatch.
__classid Obtains a Delphi class reference (metaclass) for a Delphi-style class.
__delphirtti Obtains Delphi TypeInfo for a publishable type.
__declspec(delphirtti) Requests generation of Delphi-compatible RTTI for appropriate C++ declarations.
__finally Defines a termination block that executes regardless of how the preceding protected block exits. It is commonly encountered in Delphi-style and legacy C++Builder code; modern C++ will often use RAII for resource cleanup instead.
__try / __except Provide access to Windows Structured Exception Handling (SEH). These are distinct from normal C++ exception handling.
__declspec(delphiclass) Identifies a Delphi-style class, providing Delphi-compatible RTTI, construction/destruction and exception behaviour. Particularly important in forward declarations of Delphi-style classes.
__declspec(delphirecord) Identifies a structure requiring Delphi-record-compatible handling, particularly across the Delphi/C++ interoperability boundary.
__declspec(pascalimplementation) Identifies a class whose implementation was compiled by Delphi. Primarily encountered in generated .hpp interoperability headers rather than application code.

Common Idioms

C++Builder has evolved over many years, so you'll encounter a mixture of Delphi-influenced C++Builder idioms and modern C++ in existing codebases. Here are some common patterns you'll find in the wild, together with modern approaches recommended by Embarcadero where appropriate.

Resource Cleanup

A traditional C++Builder idiom uses __try and __finally to ensure that an object is destroyed regardless of how the protected block exits:

auto strings = new TStringList();

__try {
    strings->Add(L"Delphi");
    strings->Add(L"C++Builder");
}
__finally {
    delete strings;
}

You'll encounter this pattern frequently in existing C++Builder code. With modern C++, Embarcadero recommends using RAII and smart pointers where appropriate instead:

auto strings = std::make_unique<TStringList>();

strings->Add(L"Delphi");
strings->Add(L"C++Builder");

Here std::unique_ptr owns the object and automatically destroys it when the pointer leaves scope, including during exception unwinding. This removes the need for an explicit cleanup block.

Delphi Exceptions

Exceptions raised by Delphi libraries can be caught using normal C++ exception handling. Delphi exception objects should be thrown by value and caught by reference.

throw Exception(L"Something went wrong");

The exception can then be caught by reference:

try {
    DoSomething();
}
catch (Exception& e) {
    ShowMessage(e.Message);
}

C++Builder manages the lifetime of a caught Delphi exception object, so the handler should not delete it.

Conversely, C++ exceptions should generally be caught before they escape into Delphi framework code. An uncaught standard C++ exception crossing into the Delphi RTL can be surfaced as an external exception rather than retaining its normal C++ exception semantics.

Delphi's exception hierarchy is declared in System.SysUtils.hpp. Exception is the base class for run-time exceptions, with derived classes including EArgumentException, EConvertError, EFileNotFoundException and EOSError. Custom exception classes normally derive from Exception or an appropriate subclass:

class EOrderException : public Exception {
private:
    int fErrorCode;

public:
    __fastcall EOrderException(int ErrorCode, const UnicodeString& Message)
        : Exception(Message), fErrorCode(ErrorCode) {
    }

    __property int ErrorCode = { read=fErrorCode };
};

The custom exception can then be thrown by value and caught by reference:

throw EOrderException(42, L"Unable to process order");

// ...

catch (EOrderException& e) {
    ShowMessage(e.Message);
}

Structured Exception Handling

C++Builder also provides access to Windows Structured Exception Handling (SEH). This is a different exception mechanism from normal C++ try / catch:

__try {
    DangerousWindowsOperation();
}
__except (EXCEPTION_EXECUTE_HANDLER) {
    // Handle the structured exception
}

The __try / __except construct handles Windows structured exceptions, including exceptions originating from the operating system or hardware. It should not be confused with standard C++ exception handling.

In the Modern C++ toolchain, C++ exception handling and SEH must not be mixed within the same function. They can coexist within an application, but code requiring both mechanisms should separate them across function boundaries.

Component Ownership

VCL component ownership is another place where ordinary C++ ownership assumptions should not be applied automatically. Passing an owner to a component constructor places its lifetime under the VCL component ownership mechanism:

auto button = new TButton(this);
button->Parent = this;

Here this owns the button and will destroy it when the owner itself is destroyed. The Parent relationship separately determines its visual containment.

For ordinary C++ objects outside such framework-managed ownership, modern C++ RAII remains appropriate:

auto service = std::make_unique<TMyService>();

The important distinction is not simply between raw pointers and smart pointers, but between objects whose lifetime is managed by a framework ownership mechanism and objects whose lifetime your C++ code must manage itself.

Invoking Events

A component exposing an event normally tests whether a handler has been assigned before invoking its closure. The following example invokes the assigned method on its associated object instance, passing the component raising the event as Sender.

if (fOnChanged) {
    fOnChanged(this);
}

These examples illustrate a useful general principle when working with C++Builder: understand the Delphi and VCL mechanisms you'll encounter, but continue to use standard modern C++ techniques where they provide the clearer and safer implementation.


Conclusion

Delphi is a vast ecosystem built around developer productivity, and there is much we haven't covered here, including COM, attributes, generics and their interaction with C++ templates, TValue, RTTI APIs, streaming internals, actions, data modules, message handling, FireMonkey, deployment, memory management internals and the deeper mechanics of packages.

For further information, please see the documentation and the resources linked from the introduction.