Tehnički članak

Delphi Excel komponente kompatibilne s C++Builderom

Prilikom održavanja VCL komponente napisane u Delphiju koju koriste i korisnici Delphija i C++Buildera, brzo shvatite da ta dva sustava za izgradnju vrlo različito tretiraju ovisnosti. Jedinica (unit) koja se savršeno prevodi u Delphiju može uzrokovati katastrofalne greške povezivača (linker errors) u C++Builderu. Ovo neslaganje česta je zamka za autore komponenti, posebno pri dodavanju novih internih jedinica u postojeću biblioteku

Ovdje je detaljna raščlamba zašto se to događa, koristeći primjere iz stvarnog svijeta iz komponente HotXLS, i kako osigurati vaš proces unakrsnog prevođenja pomoću eksplicitnih uključivanja, {$HPPEMIT} i pragma povezivanja

Zamka: Implicitno prevođenje naspram eksplicitnog uključivanja

Pretpostavimo da kreirate novu Delphi jedinicu, lxXlsSummary.pas, za rukovanje metapodacima dokumenta i dodate je u uses u vašoj glavnoj jedinici za parsiranje, lxRead.pas. Pritisnete compile (prevedi) u Delphi IDE-u, izrada (build) pozeleni i isporučite ažuriranje

Sljedeći dan vaši korisnici C++Buildera prijave grešku tijekom faze povezivanja: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Delphi način (dcc32)

Kada Delphi prevoditelj obrađuje paket (.dpk), on gleda jedinice koje su eksplicitno navedene u klauzuli contains. Ako jedna od tih jedinica koristi (uses) vanjsku jedinicu (poput lxXlsSummary.pas) koja nije na popisu contains, Delphi prevoditelj izvodi implicitno statičko povezivanje. On jednostavno pronalazi .pas datoteku u putanji pretraživanja, prevodi je u .dcu i ugrađuje je u rezultirajući .bpl. Izrada uspijeva, potpuno maskirajući propust

C++Builder način (MSBuild / .cbproj)

Sustav izgradnje C++Buildera mnogo je stroži. On generira samo C++ datoteke objekata (.obj) i zaglavlja (.hpp) za Delphi jedinice eksplicitno navedene u grupi stavki <DelphiCompile> unutar datoteke .cbproj. Budući da lxXlsSummary.pas nikada nije bio eksplicitno registriran u datoteci projekta, ne stvara se lxXlsSummary.obj. Kada povezivač (linker) pokuša razriješiti pozive koje vrši lxRead.obj, simboli nedostaju, što dovodi do greške neriješene vanjske reference

Usporedba build tijeka za HotXLS Delphi komponentu: Delphi dcc32 implicitno kompajlira nenavedenu jedinicu u paket i gradi zeleno, dok C++Builder MSBuild nikad ne generira njenu objektnu datoteku pa ILINK32 odustaje s nerazriješenim vanjskim simbolom
Ista neizlistana jedinica kreće se dva različita puta: dcc32 izostanak maskira kroz implicitno statičko povezivanje, dok ga stroži C++Builder linker izlaže

Rješavanje vanjskih referenci pomoću Pragma Link i HPPEMIT

Ako želite osigurati da je jedinica pravilno povezana u C++ bez prisiljavanja korisnika da ručno doda .obj datoteku u svoj projekt, možete koristiti Delphi direktivu {$HPPEMIT}. Ovo govori Delphi prevoditelju da ubaci specifičnu C++ #pragma link direktivu u generiranu .hpp datoteku

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Umetni pragma link u generiranu C++ header datoteku
  // Ovo prisiljava C++ linker da uključi odgovarajuću .obj datoteku
  {$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;
  // Ovdje logika izdvajanja metapodataka
end;

end.

Kada C++Builder uključi lxXlsSummary.hpp, prevoditelj nailazi na #pragma link i automatski naređuje povezivaču (ILINK32/ILINK64) da razriješi simbole iz lxXlsSummary.obj

Četverostupčani tok mehanizma HPPEMIT pragma povezivanja u Delphiju: deklarirana direktiva tjera dcc32 da ubaci linker naredbu u lxXlsSummary.hpp, C++Builder je troši pri include-u, a ILINK32 automatski razrješava XlsReadSummaryInformation
Linker redoslijed piše se jednom u Pascal izvoru, putuje unutar generiranog zaglavlja i stiže u svaki budući C++ build bez ručnih izmjena projekta

Zlatno pravilo za održavanje komponenti

Kako biste u potpunosti izbjegli uništavanje izrade u C++Builderu, morate usvojiti strogu politiku registracije. Kad god se u vašu biblioteku doda nova Pascal jedinica, ona mora biti eksplicitno registrirana u sve tri vrste projektnih datoteka istovremeno

1. Ažurirajte C++Builder projekt (.cbproj / .bpk)

Otvorite .cbproj datoteku u uređivaču teksta i dodajte novu jedinicu na popis za prevođenje, osiguravajući da pružite jedinstveni redoslijed izrade (build order). Ako koristite starije verzije C++Buildera s .bpk datotekama, osigurajte da je dodana oznaka <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>

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

2. Ažurirajte Delphi paket (.dpk)

Dodajte jedinicu eksplicitnoj klauzuli contains. Ovo osigurava da se Delphi prevoditelj ne mora oslanjati na implicitno povezivanje, što se ionako općenito smatra lošom praksom

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Validacija kontinuirane integracije

Krajnja obrana od ove zamke je CI/CD validacija. Nikada se ne oslanjajte isključivo na uspješnu izradu Delphija prije isporuke dvojezične komponente. Vaše skripte za izradu moraju pozvati MSBuild ili alate naredbenog retka bcc32c na projektima C++Buildera (npr. build-Win32-Lib-CB.cmd) i pokrenuti potpuno povezivanje C++ probnih verzija i cjelovitih demonstracija. Tek kada C++ povezivač uspije, možete biti sigurni da su sve Delphi jedinice ispravno registrirane i da izlažu svoje simbole C++ okruženju za izvođenje (runtime)

Napomena: Kompatibilnost prevoditelja na više platformi strogo se održava u Delphi i C++Builder izdanjima HotXLS Delphi VCL Component