技術記事

C++Builder向けのDelphiコンポーネントのクロスコンパイル: 未解決の外部シンボルの回避

Delphiで記述され、DelphiとC++Builderの両方のユーザーによって使用されるVCLコンポーネントを保守していると、2つのビルドシステムが依存関係をまったく異なる方法で処理することにすぐに気づきます。Delphiで完全にコンパイルされるユニットが、C++Builderで致命的なリンカーエラーを引き起こす可能性があります。この不一致は、特に既存のライブラリに新しい内部ユニットを追加する場合、コンポーネント作成者にとってよくある罠です

ここでは、HotXLSコンポーネントの実例を使い、なぜこの問題が起きるのか、明示的なインクルード、{$HPPEMIT}、プラグマリンクでクロスコンパイルを安定させる方法を詳しく説明します

罠: 暗黙のコンパイルと明示的なインクルード

ドキュメントメタデータを処理するための新しいDelphiユニットlxXlsSummary.pasを作成し、それをメインの解析ユニットlxRead.pasusesするとします。Delphi IDEでコンパイルを実行すると、ビルドは成功し、アップデートを出荷します

翌日、C++Builderのユーザーから、リンクフェーズでUnresolved external 'XlsReadSummaryInformation' referenced from lxRead.objというエラーが報告されます

Delphiの方法 (dcc32)

Delphiコンパイラがパッケージ(.dpk)を処理するとき、contains句に明示的にリストされているユニットを参照します。それらのユニットの1つが、containsリストにない外部ユニット(lxXlsSummary.pasなど)をusesしている場合、Delphiコンパイラは暗黙の静的リンクを実行します。検索パスで.pasファイルを見つけて.dcuにコンパイルし、結果の.bplに焼き付けるだけです。ビルドは成功し、この省略は完全に隠蔽されます

C++Builderの方法 (MSBuild / .cbproj)

C++Builderのビルドシステムははるかに厳格です。.cbprojファイルの<DelphiCompile>アイテムグループに明示的にリストされているDelphiユニットのC++オブジェクトファイル(.obj)とヘッダー(.hpp)のみを生成します。lxXlsSummary.pasがプロジェクトファイルに明示的に登録されていなかったため、lxXlsSummary.objは作成されません。リンカーがlxRead.objによる呼び出しを解決しようとすると、シンボルが欠落しているため、未解決の外部エラーが発生します

プラグマリンクとHPPEMITを使用した外部の解決

ユーザーに手動で.objファイルをプロジェクトに追加させることなく、ユニットがC++で適切にリンクされるようにしたい場合は、Delphiの{$HPPEMIT}ディレクティブを使用できます。これは、生成された.hppファイルに特定のC++ #pragma linkディレクティブを挿入するようにDelphiコンパイラに指示します

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Inject a pragma link into the generated C++ header file
  // This forces the C++ linker to include the corresponding .obj file
  {$HPPEMIT '#pragma link "lxXlsSummary.obj"'}
{$ENDIF}

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.

C++BuilderがlxXlsSummary.hppをインクルードすると、コンパイラは#pragma linkを検出し、リンカー(ILINK32/ILINK64)にlxXlsSummary.objからシンボルを解決するように自動的に指示します

コンポーネント保守の黄金律

C++Builderのビルドが完全に壊れるのを避けるには、厳格な登録ポリシーを採用する必要があります。Pascalユニットがライブラリに新しく追加されるたびに、3つのプロジェクトファイルタイプすべてで同時に明示的に登録する必要があります

1. C++Builderプロジェクト (.cbproj / .bpk) の更新

テキストエディタで.cbprojファイルを開き、コンパイルリストに新しいユニットを追加して、一意のビルド順序(BuildOrder)を確実に指定します。.bpkファイルを使用する古いバージョンのC++Builderを使用している場合は、<file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>タグが追加されていることを確認してください

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

2. Delphiパッケージ (.dpk) の更新

明示的なcontains句にユニットを追加します。これにより、Delphiコンパイラは、一般的に悪い習慣と見なされている暗黙のリンクに依存する必要がなくなります

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

継続的インテグレーションによる検証

この罠に対する究極の防御策は、CI/CD検証です。デュアル言語コンポーネントを出荷する前に、Delphiのビルド成功だけに依存してはなりません。ビルドスクリプトは、C++Builderプロジェクト(たとえば、build-Win32-Lib-CB.cmd)でMSBuildまたはbcc32cコマンドラインツールを呼び出し、C++トライアルおよび完全なデモの完全なリンクを実行する必要があります。C++リンカーが成功した場合にのみ、すべてのDelphiユニットが正しく登録され、そのシンボルがC++ランタイムに公開されていると確信できます

注: クロスプラットフォームのコンパイラ互換性は、HotXLS VCL ComponentのDelphi版およびC++Builder版全体で厳密に維持されています