Când întrețineți o componentă VCL scrisă în Delphi, dar consumată de utilizatori atât de Delphi, cât și de C++Builder, realizați rapid că cele două sisteme de build tratează dependențele foarte diferit. O unitate care se compilează perfect în Delphi ar putea cauza erori catastrofale de linker în C++Builder. Această discrepanță este o capcană frecventă pentru autorii de componente, în special atunci când adaugă noi unități interne la o bibliotecă existentă
Iată o detaliere a motivului pentru care se întâmplă acest lucru (folosind exemple din lumea reală din componenta HotXLS) și cum să vă protejați procesul de compilare încrucișată utilizând includeri explicite, {$HPPEMIT} și legarea pragma (pragma linking)
Capcana: Compilare implicită vs. Includere explicită
Să presupunem că creați o nouă unitate Delphi, lxXlsSummary.pas, pentru a gestiona metadatele documentului și o utilizați prin uses în unitatea dumneavoastră principală de parsare, lxRead.pas. Apăsați butonul de compilare în IDE-ul Delphi, build-ul este verde și lansați actualizarea
A doua zi, utilizatorii dumneavoastră C++Builder raportează o eroare în timpul etapei de link-editare: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj (Referință externă nerezolvată)
Abordarea Delphi (dcc32)
Când compilatorul Delphi procesează un pachet (.dpk), acesta se uită la unitățile listate explicit în clauza contains. Dacă una dintre acele unități apelează prin uses o unitate externă (precum lxXlsSummary.pas) care nu se află în lista contains, compilatorul Delphi efectuează o legare statică implicită. Pur și simplu găsește fișierul .pas în calea de căutare, îl compilează într-un .dcu și îl integrează în .bpl rezultat. Build-ul are succes, mascând complet omisiunea
Abordarea C++Builder (MSBuild / .cbproj)
Sistemul de build al C++Builder este mult mai strict. Acesta generează fișiere obiect C++ (.obj) și headere (.hpp) doar pentru unitățile Delphi enumerate explicit în grupul de elemente <DelphiCompile> al fișierului .cbproj. Deoarece lxXlsSummary.pas nu a fost niciodată înregistrat explicit în fișierul de proiect, nu este creat niciun lxXlsSummary.obj. Când linker-ul încearcă să rezolve apelurile făcute de lxRead.obj, simbolurile lipsesc, ducând la o eroare externă nerezolvată
Rezolvarea referințelor externe cu Pragma Link și HPPEMIT
Dacă doriți să vă asigurați că o unitate este legată corect în C++ fără a forța utilizatorul să adauge manual fișierul .obj la proiectul său, puteți utiliza directiva {$HPPEMIT} din Delphi. Aceasta indică compilatorului Delphi să injecteze o directivă C++ #pragma link specifică în fișierul .hpp generat
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ând C++Builder include lxXlsSummary.hpp, compilatorul întâlnește #pragma link și îi spune automat linker-ului (ILINK32/ILINK64) să rezolve simbolurile din lxXlsSummary.obj
Regula de aur pentru întreținerea componentelor
Pentru a evita defectarea completă a build-urilor C++Builder, trebuie să adoptați o politică strictă de înregistrare. Ori de câte ori se adaugă o nouă unitate Pascal la biblioteca dumneavoastră, aceasta trebuie înregistrată explicit în toate cele trei tipuri de fișiere de proiect simultan
1. Actualizați proiectul C++Builder (.cbproj / .bpk)
Deschideți fișierul .cbproj într-un editor de text și adăugați noua unitate la lista de compilare, asigurându-vă că oferiți o ordine de build unică. Dacă utilizați versiuni mai vechi de C++Builder cu fișiere `.bpk`, asigurați-vă că este adăugată eticheta <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>
<DelphiCompile Include="lxXlsSummary.pas">
<BuildOrder>101</BuildOrder>
</DelphiCompile>
2. Actualizați pachetul Delphi (.dpk)
Adăugați unitatea în clauza explicită contains. Acest lucru asigură faptul că compilatorul Delphi nu trebuie să se bazeze pe legarea implicită, care, oricum, este considerată, în general, o practică proastă
package HotXLS;
{$R *.res}
{$ALIGN 8}
{$ASSERTIONS ON}
{$BOOLEVAL OFF}
requires
rtl,
vcl;
contains
lxRead in 'lxRead.pas',
lxXlsSummary in 'lxXlsSummary.pas';
end.
Validarea integrării continue (CI)
Apărarea supremă împotriva acestei capcane este validarea CI/CD. Nu vă bazați niciodată doar pe un build Delphi de succes înainte de a lansa o componentă duală (pentru două limbaje). Scripturile dumneavoastră de build trebuie să invoce MSBuild sau instrumentele din linia de comandă bcc32c pe proiectele C++Builder (de exemplu, build-Win32-Lib-CB.cmd) și să ruleze o legare completă a demo-urilor C++ de încercare și complete. Doar atunci când linker-ul C++ reușește puteți fi sigur că toate unitățile Delphi sunt înregistrate corect și își expun simbolurile mediului de execuție C++
Notă: Compatibilitatea compilatorului multiplatformă este menținută cu strictețe pentru edițiile Delphi și C++Builder ale HotXLS VCL Component