Techninis straipsnis

Delphi komponentų kryžminis kompiliavimas (angl. cross-compiling) C++Builder programai: išvengiant neišspręstų išorinių simbolių

Prižiūrint VCL komponentą, parašytą naudojant Delphi, bet vartojamą tiek Delphi, tiek C++Builder naudotojų, greitai suprantate, kad šios dvi konstravimo sistemos priklausomybes vertina labai skirtingai. Modulis (angl. unit), kuris puikiai kompiliuojamas Delphi aplinkoje, gali sukelti katastrofiškas saistytuvo (angl. linker) klaidas C++Builder programoje. Šis neatitikimas yra dažni spąstai komponentų autoriams, ypač kai į esamą biblioteką pridedami nauji vidiniai moduliai

Štai išsami analizė, kodėl taip nutinka, naudojant realaus pasaulio pavyzdžius iš „HotXLS“ komponento, ir kaip padaryti jūsų kryžminio kompiliavimo procesą nepramušamu (angl. bulletproof) naudojant aiškius įtraukimus (angl. explicit includes), {$HPPEMIT} ir pragma saistymą (angl. linking)

Spąstai: numanomas kompiliavimas prieš aiškų įtraukimą

Tarkime, sukuriate naują Delphi modulį lxXlsSummary.pas, skirtą dokumento metaduomenims apdoroti, ir naudojate (angl. uses) jį savo pagrindiniame analizės modulyje lxRead.pas. Paspaudžiate „compile“ Delphi IDE aplinkoje, konstravimas pažaliuoja ir jūs išsiunčiate naujinimą (angl. update)

Kitą dieną jūsų C++Builder naudotojai praneša apie klaidą saistymo etape: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Delphi būdas (dcc32)

Kai Delphi kompiliatorius apdoroja paketą (.dpk), jis peržiūri modulius, aiškiai išvardytus contains sąlygoje. Jei vienas iš tų modulių naudoja (angl. uses) išorinį modulį (pvz., lxXlsSummary.pas), kurio nėra contains sąraše, Delphi kompiliatorius atlieka numanomą statinį saistymą (angl. implicit static linking). Jis tiesiog randa .pas failą paieškos kelyje, sukompiliuoja jį į .dcu ir „įkepa“ į galutinį .bpl. Konstravimas pavyksta, visiškai užmaskuodamas praleidimą

C++Builder būdas (MSBuild / .cbproj)

C++Builder konstravimo sistema yra daug griežtesnė. Ji generuoja C++ objekto failus (.obj) ir antraštes (.hpp) tik tiems Delphi moduliams, kurie aiškiai išvardyti .cbproj failo <DelphiCompile> elementų grupėje. Kadangi lxXlsSummary.pas niekada nebuvo aiškiai užregistruotas projekto faile, lxXlsSummary.obj nesukuriamas. Kai saistytojas bando išspręsti iškvietimus (angl. calls), kuriuos padarė lxRead.obj, trūksta simbolių, o tai sukelia neišspręsto išorinio simbolio klaidą

HotXLS Delphi komponento kūrimo grandinės palyginimas: Delphi dcc32 netiesiogiai sukompiliuoja nesąraše esantį modulį į paketą ir sukuria žaliai, o C++Builder MSBuild niekada nesugeneruoja savo objektinio failo ir ILINK32 nutraukia su neišspręsta išorine nuoroda
Tas pats neišvardytasis modulis eina dvi skirtingas kryptis: dcc32 užgožia praleidimą per netiesioginį statinį susiejimą, o griežtesnysis C++Builder linkeris jį atskleidžia

Išorinių simbolių išsprendimas su Pragma Link ir HPPEMIT

Jei norite užtikrinti, kad modulis būtų tinkamai susietas C++ aplinkoje, neverčiant naudotojo rankiniu būdu pridėti .obj failo į savo projektą, galite naudoti Delphi {$HPPEMIT} direktyvą. Tai nurodo Delphi kompiliatoriui įterpti konkrečią C++ #pragma link direktyvą į sugeneruotą .hpp failą

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Įterpti pragma link nurodymą į sugeneruotą C++ antraštės failą
  // Tai priverčia C++ linkerį įtraukti atitinkamą .obj failą
  {$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;
  // Metaduomenų išgavimo logika čia
end;

end.

Kai C++Builder įtraukia lxXlsSummary.hpp, kompiliatorius susiduria su #pragma link ir automatiškai nurodo saistytojui (ILINK32/ILINK64) išspręsti simbolius iš lxXlsSummary.obj

Keturių stulpelių HPPEMIT pragma susiejimo mechanizmo Delphi srautas: deklaruota direktyva verčia dcc32 įšvirkšti susaistytojo tvarką į lxXlsSummary.hpp, C++Builder jos suvartojama įtraukiant, o ILINK32 automatiškai išsprendžia XlsReadSummaryInformation
Linkerio tvarka užrašoma vieną kartą Pascal šaltinyje, keliauja sugeneruotojoje antraštėje ir pasiekia kiekvieną būsimąjį C++ build be rankinio projekto redagavimo

Auksinė komponentų priežiūros taisyklė

Norėdami išvengti visiško C++Builder konstravimų sugadinimo, turite priimti griežtą registracijos politiką. Kaskart, kai į jūsų biblioteką pridedamas naujas Pascal modulis, jis turi būti aiškiai užregistruotas visų trijų tipų projektų failuose vienu metu

1. Atnaujinkite C++Builder projektą (.cbproj / .bpk)

Atidarykite .cbproj failą teksto rengyklėje ir pridėkite naują modulį į kompiliavimo sąrašą, užtikrindami, kad pateiktumėte unikalią konstravimo tvarką (angl. build order). Jei naudojate senesnes C++Builder versijas su .bpk failais, įsitikinkite, kad pridėta žyma <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>

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

2. Atnaujinkite Delphi paketą (.dpk)

Pridėkite modulį prie aiškios contains sąlygos. Tai užtikrina, kad Delphi kompiliatoriui nereikės kliautis numanomu saistymu, kas bet kuriuo atveju paprastai laikoma bloga praktika

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Nuolatinės integracijos (angl. Continuous Integration) patvirtinimas

Geriausia gynyba nuo šių spąstų yra CI/CD patvirtinimas. Niekada nepasikliaukite vien tik sėkmingu Delphi konstravimu prieš išsiųsdami dviejų kalbų (angl. dual-language) komponentą. Jūsų konstravimo skriptai turi iškviesti MSBuild arba bcc32c komandų eilutės įrankius C++Builder projektuose (pvz., build-Win32-Lib-CB.cmd) ir atlikti pilną C++ bandomosios (angl. trial) ir pilnos versijų demonstracinių programų saistymą. Tik tada, kai C++ saistytojas sėkmingai baigia darbą, galite būti tikri, kad visi Delphi moduliai yra teisingai užregistruoti ir atskleidžia savo simbolius C++ vykdymo aplinkai (angl. runtime)

Pastaba: Kelių platformų (angl. cross-platform) kompiliatorių suderinamumas yra griežtai palaikomas HotXLS Delphi VCL Component Delphi ir C++Builder leidimuose