Technical Article

Cross-Compiling Delphi Components for C++Builder: Avoiding Unresolved Externals

When maintaining a VCL component written in Delphi but consumed by both Delphi and C++Builder users, you quickly realize that the two build systems treat dependencies very differently. A unit that compiles cleanly in Delphi can still break the link step in C++Builder. This discrepancy is a frequent trap for component authors, particularly when adding new internal units to an existing library

Here is a detailed breakdown of why this happens, using the unit layout of the HotXLS Delphi component as the worked example, and how to keep both builds working with explicit unit registration, {$HPPEMIT}, and pragma linking

The Trap: Implicit Compilation vs. Explicit Inclusion

Suppose you create a new Delphi unit, lxXlsSummary.pas, to handle document metadata, and you uses it in your main parsing unit, lxRead.pas. You hit compile in the Delphi IDE, the build turns green, and you ship the update

The next day, your C++Builder users report an error during the linking phase: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

The Delphi Way (dcc32)

When the Delphi compiler processes a package (.dpk), it looks at the units explicitly listed in the contains clause. If one of those units uses an external unit (like lxXlsSummary.pas) that is not in the contains list, the Delphi compiler performs implicit static linking. It simply finds the .pas file in the search path, compiles it into a .dcu, and bakes it into the resulting .bpl. The build succeeds, and the only sign of the omission is a compiler warning that the unit was implicitly imported into the package, the kind of line that scrolls past unread in a long build log

The C++Builder Way (MSBuild / .cbproj)

C++Builder’s build system is much stricter. It only generates C++ object files (.obj) and headers (.hpp) for the Delphi units explicitly listed in the <DelphiCompile> item group of the .cbproj file. Because lxXlsSummary.pas was never explicitly registered in the project file, no lxXlsSummary.obj is created. When the linker tries to resolve the calls made by lxRead.obj, the symbols are missing, leading to an unresolved external error

Build pipeline comparison for the HotXLS Delphi component: Delphi dcc32 implicitly compiles an unlisted unit into the package and builds green, while C++Builder MSBuild never generates its object file and ILINK32 aborts with an unresolved external
The same unlisted unit takes two divergent paths: dcc32 masks the omission through implicit static linking while the stricter C++Builder linker exposes it

Resolving Externals with Pragma Link and HPPEMIT

If you want to ensure that a unit is properly linked in C++ without forcing the user to manually add the .obj file to their project, you can use Delphi's {$HPPEMIT} directive. This tells the Delphi compiler to inject a specific C++ #pragma link directive into the generated .hpp file

unit lxXlsSummary;

interface

// Inject a pragma link into the generated C++ header file. Delphi-only
// builds ignore HPPEMIT, so the directive needs no conditional around it
{$HPPEMIT '#pragma link "lxXlsSummary"'}

uses
  SysUtils, Classes;

type
  TXlsSummaryInfo = class(TObject)
  public
    Title: string;
    Author: string;
    CreateTime: TDateTime;
  end;

function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;

implementation

function XlsReadSummaryInformation(const FileName: string): TXlsSummaryInfo;
begin
  Result := TXlsSummaryInfo.Create;
  // Metadata extraction logic here
end;

end.

When C++Builder includes lxXlsSummary.hpp, the compiler encounters the #pragma link and automatically tells the linker (ILINK32/ILINK64) to resolve symbols from lxXlsSummary.obj, or lxXlsSummary.o with the Clang-based 64-bit toolchain. Leaving the extension off the pragma lets each toolchain pick its own object file

Four-column flow of the HPPEMIT pragma link mechanism in Delphi: a declared directive makes dcc32 inject the linker order into lxXlsSummary.hpp, C++Builder consumes it on include, and ILINK32 resolves XlsReadSummaryInformation automatically
The linker order is written once in Pascal source, travels inside the generated header, and reaches every future C++ build without manual project edits

Register every new unit in both projects

The fix is a habit rather than a tool. Whenever a new Pascal unit is added to your library, register it in both project files in the same change: the C++Builder project (.cbproj, or .bpk in older versions) and the Delphi package (.dpk)

1. Update the C++Builder Project (.cbproj / .bpk)

Open the .cbproj file in a text editor and add the new unit to the compile list, ensuring you provide a unique build order. If using older C++Builder versions with .bpk files, ensure the <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> tag is added

<DelphiCompile Include="lxXlsSummary.pas">
  <BuildOrder>101</BuildOrder>
</DelphiCompile>

2. Update the Delphi Package (.dpk)

Add the unit to the explicit contains clause. This ensures the Delphi compiler doesn't have to rely on implicit linking, which is generally considered bad practice anyway

package HotXLS;

{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}

requires
  rtl,
  vcl;

contains
  lxRead in 'lxRead.pas',
  lxXlsSummary in 'lxXlsSummary.pas';

end.

Continuous Integration Validation

Habits slip, so back the rule with a build check. Never rely solely on a successful Delphi build before shipping a dual-language component: have the build script run MSBuild on the C++Builder package project and then link at least one C++Builder demo application against it, because a package can build while a missing unit only surfaces when an application links. Only when the C++ linker succeeds can you be certain that all Delphi units are correctly registered and exposing their symbols to the C++ runtime

The unit layout used as the example here is the one the HotXLS Delphi VCL Component ships with, built from the same sources for both Delphi and C++Builder