Műszaki cikk

Delphi komponensek keresztfordítása C++Builderhez: Feloldatlan külső hivatkozások elkerülése

Amikor egy Delphiben írt, de mind a Delphi, mind a C++Builder felhasználók által használt VCL komponenst tart fenn, hamar rájön, hogy a két fordítási (build) rendszer nagyon eltérően kezeli a függőségeket. Egy Delphiben tökéletesen leforduló egység (unit) katasztrofális linker hibákat okozhat a C++Builderben. Ez az eltérés gyakori csapda a komponensszerzők számára, különösen akkor, ha új belső egységeket adnak hozzá egy meglévő könyvtárhoz

Íme egy részletes lebontás arról, hogy miért történik ez—a HotXLS Delphi komponens valós példáját felhasználva—, és hogyan teheti golyóállóvá a keresztfordítási folyamatot explicit beillesztések (includes), a {$HPPEMIT} és a pragma linkelés használatával

A csapda: Implicit fordítás kontra explicit felvétel

Tegyük fel, hogy létrehoz egy új Delphi egységet, lxXlsSummary.pas néven, a dokumentum metaadatainak kezelésére, és uses paranccsal behívja a fő elemző egységbe, az lxRead.pas fájlba. Megnyomja a fordítás gombot a Delphi IDE-ben, a build zöld lesz, és Ön kiadja a frissítést

Másnap a C++Builder felhasználói hibát jeleznek a linkelési szakaszban: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

A Delphi módszer (dcc32)

Amikor a Delphi fordító feldolgoz egy csomagot (.dpk), megnézi a contains záradékban kifejezetten felsorolt egységeket. Ha az egyik ilyen egység uses-szal egy olyan külső egységet (például lxXlsSummary.pas) használ, amely nem szerepel a contains listán, a Delphi fordító implicit statikus linkelést hajt végre. Egyszerűen megtalálja a .pas fájlt a keresési útvonalon, lefordítja .dcu fájllá, és beégeti a kapott .bpl-be. A fordítás sikeres lesz, teljesen elfedve a kihagyást

A C++Builder módszer (MSBuild / .cbproj)

A C++Builder build rendszere sokkal szigorúbb. Csak azokhoz a Delphi egységekhez generál C++ objektumfájlokat (.obj) és fejléceket (.hpp), amelyek kifejezetten fel vannak sorolva a .cbproj fájl <DelphiCompile> elemcsoportjában. Mivel az lxXlsSummary.pas soha nem lett kifejezetten regisztrálva a projektfájlban, nem jön létre lxXlsSummary.obj. Amikor a linker megpróbálja feloldani az lxRead.obj által végzett hívásokat, a szimbólumok hiányoznak, ami feloldatlan külső hivatkozás hibához vezet

Építési folyamat összehasonlítása a HotXLS Delphi komponenshez: a Delphi dcc32 implicit módon lefordít egy nem felsorolt unitot a csomagba, és zöldre buildel, míg a C++Builder MSBuild sosem generálja az objektumfájlt, és az ILINK32 megoldatlan külső hivatkozással megszakad
Ugyanaz a fel nem sorolt unit két szétágazó utat jár: a dcc32 implicit statikus linkeléssel álcázza a hiányt, míg a szigorúbb C++Builder linker felszínre hozza

Külső hivatkozások feloldása Pragma Link és HPPEMIT segítségével

Ha biztosítani szeretné, hogy egy egység megfelelően legyen linkelve C++-ban anélkül, hogy a felhasználót arra kényszerítené, hogy manuálisan hozzáadja az .obj fájlt a projektjéhez, használhatja a Delphi {$HPPEMIT} direktíváját. Ez megmondja a Delphi fordítónak, hogy fecskendezzen be egy specifikus C++ #pragma link direktívát a generált .hpp fájlba

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Egy pragma link befecskendezése a generált C++ fejlécfájlba
  // Ez kényszeríti a C++ linkert, hogy belefoglalja a megfelelő .obj fájlt
  {$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;
  // Metaadat-kinyerési logika itt
end;

end.

Amikor a C++Builder beilleszti az lxXlsSummary.hpp fájlt, a fordító találkozik a #pragma link direktívával, és automatikusan utasítja a linkert (ILINK32/ILINK64), hogy oldja fel a szimbólumokat az lxXlsSummary.obj fájlból

A HPPEMIT pragma linkmechanizmusának négyoszlopos folyamata Delphi-ben: egy deklarált direktíva arra készteti a dcc32-t, hogy a linkerutasítást az lxXlsSummary.hpp-be injektálja, a C++Builder includekor fogyasztja, az ILINK32 pedig automatikusan feloldja az XlsReadSummaryInformation-t
A linker sorrendje egyszer megíródik a Pascal forrásban, a generált fejlécben utazik, és minden jövőbeli C++ buildbe eljut kézi projektmódosítás nélkül

Az aranyszabály a komponensek karbantartásához

A C++Builder buildek tönkretételének elkerülése érdekében szigorú regisztrációs házirendet kell elfogadnia. Amikor egy új Pascal egységet ad hozzá a könyvtárához, azt kifejezetten regisztrálni kell mind a három projektfájltípusban egyidejűleg

1. Frissítse a C++Builder projektet (.cbproj / .bpk)

Nyissa meg a .cbproj fájlt egy szövegszerkesztőben, és adja hozzá az új egységet a fordítási listához, biztosítva egyedi fordítási sorrend megadását. Ha régebbi C++Builder verziókat használ .bpk fájlokkal, győződjön meg arról, hogy a <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> címke hozzá van adva

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

2. Frissítse a Delphi csomagot (.dpk)

Adja hozzá az egységet az explicit contains záradékhoz. Ez biztosítja, hogy a Delphi fordítónak ne kelljen az implicit linkelésre támaszkodnia, ami általában amúgy is rossz gyakorlatnak számít

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Folyamatos integráció általi érvényesítés

A végső védekezés ezen csapda ellen a CI/CD érvényesítés. Soha ne hagyatkozzon kizárólag a sikeres Delphi fordításra, mielőtt egy kétnyelvű komponenst kiadna. A fordítási szkriptjeinek meg kell hívniuk az MSBuild-et vagy a bcc32c parancssori eszközöket a C++Builder projekteken (pl. build-Win32-Lib-CB.cmd), és le kell futtatniuk a C++ próba (trial) és teljes demók komplett linkelését. Csak akkor lehet biztos abban, hogy minden Delphi egység megfelelően regisztrálva van, és felfedi szimbólumait a C++ futtatókörnyezet számára, ha a C++ linker sikeresen lefut

Megjegyzés: A platformokon átívelő fordító-kompatibilitást szigorúan fenntartjuk a HotXLS Delphi VCL komponens Delphi és C++Builder kiadásai között