Technical Article

Krížová kompilácia komponentov Delphi pre C++Builder: Vyhnutie sa nevyriešeným externým odkazom

Pri údržbe komponentu VCL napísaného v Delphi, ktorý ale používajú používatelia Delphi aj C++Builder, rýchlo zistíte, že tieto dva systémy spracúvajú závislosti veľmi odlišne. Jednotka, ktorá sa bezchybne kompiluje v Delphi, môže v C++Builderi spôsobiť katastrofálne chyby prepojovača (linker). Tento nesúlad je častou pascou pre autorov komponentov, najmä pri pridávaní nových interných jednotiek do existujúcej knižnice.

Tu je podrobný rozpis toho, prečo sa to deje, na príkladoch z reálneho sveta z komponentu HotXLS, a ako zabezpečiť proces krížovej kompilácie pomocou explicitných zahrnutí, {$HPPEMIT} a prepojenia pragma.

Pasca: Implicitná kompilácia verzus explicitné zahrnutie

Predpokladajme, že vytvoríte novú jednotku v Delphi s názvom lxXlsSummary.pas na spracovanie metadát dokumentu a použijete ju pomocou uses v hlavnej analytickej jednotke lxRead.pas. Spustíte kompiláciu v IDE Delphi, zostavenie prebehne úspešne a vydáte aktualizáciu.

Na druhý deň používatelia C++Buildera hlásia chybu počas fázy prepojovania: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj.

Prístup Delphi (dcc32)

Keď kompilátor Delphi spracúva balík (.dpk), preskúma jednotky explicitne uvedené v klauzule contains. Ak niektorá z týchto jednotiek používa (uses) externú jednotku (ako napríklad lxXlsSummary.pas), ktorá nie je v zozname contains, kompilátor Delphi vykoná implicitné statické prepojenie. Jednoducho nájde súbor .pas v ceste vyhľadávania, skompiluje ho do súboru .dcu a vloží ho do výsledného súboru .bpl. Zostavenie je úspešné a vynechanie sa úplne maskuje.

Prístup C++Builder (MSBuild / .cbproj)

Systém zostavovania C++Buildera je oveľa prísnejší. Generuje objektové súbory C++ (.obj) a hlavičky (.hpp) iba pre jednotky Delphi explicitne uvedené v skupine položiek <DelphiCompile> v súbore .cbproj. Pretože súbor lxXlsSummary.pas nebol v projekte nikdy explicitne zaregistrovaný, súbor lxXlsSummary.obj sa nevytvorí. Keď sa linker pokúsi vyriešiť volania z lxRead.obj, symboly chýbajú, čo vedie k chybe nevyriešeného externého odkazu.

Riešenie externých odkazov pomocou Pragma Link a HPPEMIT

Ak chcete zabezpečiť, aby bola jednotka v C++ správne prepojená bez toho, aby ste používateľa nútili ručne pridávať súbor .obj do projektu, môžete použiť direktívu Delphi {$HPPEMIT}. Tá kompilátoru Delphi prikáže vložiť špecifickú direktívu C++ #pragma link do generovaného súboru hlavičky .hpp.

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.

Keď C++Builder zahrnie súbor lxXlsSummary.hpp, kompilátor narazí na direktívu #pragma link a automaticky povie linkeru (ILINK32/ILINK64), aby vyriešil symboly zo súboru lxXlsSummary.obj.

Zlaté pravidlo pre údržbu komponentov

Aby ste sa úplne vyhli zničeniu zostavení C++Buildera, musíte prijať prísnu politiku registrácie. Kedykoľvek sa do knižnice pridá nová jednotka Pascal, musí byť explicitne a súčasne zaregistrovaná vo všetkých troch typoch projektových súborov.

1. Aktualizácia projektu C++Builder (.cbproj / .bpk)

Otvorte súbor .cbproj v textovom editore a pridajte novú jednotku do zoznamu kompilácie, pričom jej pridelíte jedinečné poradie zostavovania. Ak používate staršie verzie C++Builderu so súbormi `.bpk`, uistite sa, že je pridaná značka ``.

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

2. Aktualizácia balíka Delphi (.dpk)

Pridajte jednotku do explicitnej klauzuly contains. To zaisťuje, že sa kompilátor Delphi nemusí spoliehať na implicitné prepájanie, ktoré sa vo všeobecnosti považuje za zlú prax.

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Overenie kontinuálnej integrácie

Konečnou obranou proti tejto pasci je validácia CI/CD. Pri dodávaní dvojjazyčného komponentu sa nikdy nespoliehajte iba na úspešné zostavenie v Delphi. Vaše skripty na zostavovanie musia spustiť nástroje príkazového riadka MSBuild alebo bcc32c na projektoch C++Buildera (napríklad build-Win32-Lib-CB.cmd) a prepojiť plnohodnotné demoverzie a demoverzie C++. Až keď C++ linker uspeje, môžete si byť istí, že všetky jednotky Delphi sú správne zaregistrované a sprístupňujú svoje symboly v runtime prostredí C++.

Poznámka: Kompatibilita naprieč platformami pre kompilátory je striktne dodržiavaná v edíciách Delphi a C++Builder komponentu HotXLS VCL Component.