Odborný článok

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

Porovnanie zostavovacej pipeline pre komponent HotXLS Delphi: dcc32 Delphi implicitne skompiluje nevypísanú jednotku do paketu a zostaví zeleno, zatiaľ čo MSBuild C++Builder nikdy nevygeneruje jej objektový súbor a ILINK32 abortuje s nevyriešeným externým symbolom
Rovnaká neuvedená unit sa vyberie dvomi rozchádzajúcimi cestami: dcc32 maskuje vynechanie cez implicitné statické linkovanie, zatiaľ čo prísnejší linker C++Builder ho odhalí

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}
  // Vlož pragma link do vygenerovaného hlavičkového súboru C++
  // Toto vynúti linker C++, aby zahrnul zodpovedajúci súbor .obj
  {$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;
  // Tu logika extrakcie metadát
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

Štvorstĺpcový tok mechanizmu link pragma HPPEMIT v Delphi: deklarovaná direktíva prinúti dcc32 vstreknúť linker poradie do lxXlsSummary.hpp, C++Builder ho skonzumuje pri include a ILINK32 rozpozná XlsReadSummaryInformation automaticky
Poradie linkeru sa zapíše raz v zdroji Pascalu, cestuje vo vnútri vygenerovaného headeru a dostane sa do každého budúceho C++ buildu bez ručných úprav projektu

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 Delphi VCL Component