Tekninen artikkeli

Delphi-komponenttien ristiinkääntäminen C++Builderille: Ratkaisemattomien ulkoisten viitteiden välttäminen

Kun ylläpidät Delphillä kirjoitettua VCL-komponenttia, jota käyttävät sekä Delphi- että C++Builder-käyttäjät, huomaat nopeasti, että nämä kaksi koontijärjestelmää käsittelevät riippuvuuksia hyvin eri tavoin. Yksikkö, joka kääntyy täydellisesti Delphissä, saattaa aiheuttaa katastrofaalisia linkitysvirheitä C++Builderissa. Tämä ero on yleinen ansa komponenttien tekijöille, erityisesti silloin, kun olemassa olevaan kirjastoon lisätään uusia sisäisiä yksiköitä

Tässä on yksityiskohtainen erittely siitä, miksi näin tapahtuu käyttäen todellisia esimerkkejä HotXLS-komponentista, ja kuinka voit tehdä ristiinkäännösprosessistasi pomminvarman käyttämällä eksplisiittisiä sisällytyksiä, {$HPPEMIT}-direktiiviä ja pragma-linkitystä

Ansa: Implisiittinen käännös vs. eksplisiittinen sisällyttäminen

Oletetaan, että luot uuden Delphi-yksikön, lxXlsSummary.pas, käsittelemään asiakirjan metatietoja, ja lisäät sen uses-lauseeseen pääjäsentämisyksikössäsi, lxRead.pas. Painat kääntämispainiketta Delphi IDE:ssä, koontiversio muuttuu vihreäksi, ja julkaiset päivityksen

Seuraavana päivänä C++Builder-käyttäjäsi ilmoittavat virheestä linkitysvaiheen aikana: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Delphi-tapa (dcc32)

Kun Delphi-kääntäjä käsittelee paketin (.dpk), se tarkastelee contains-lausekkeessa nimenomaisesti lueteltuja yksiköitä. Jos jokin näistä yksiköistä käyttää (uses) ulkoista yksikköä (kuten lxXlsSummary.pas), jota ei ole contains-luettelossa, Delphi-kääntäjä suorittaa implisiittisen staattisen linkityksen. Se yksinkertaisesti löytää .pas-tiedoston hakupolusta, kääntää sen .dcu-tiedostoksi ja leipoo sen sisään tuloksena olevaan .bpl-tiedostoon. Käännös onnistuu piilottaen puutteen täysin

C++Builder-tapa (MSBuild / .cbproj)

C++Builderin koontijärjestelmä on paljon tiukempi. Se tuottaa C++-objektitiedostoja (.obj) ja otsikkotiedostoja (.hpp) vain niille Delphi-yksiköille, jotka on eksplisiittisesti lueteltu .cbproj-tiedoston <DelphiCompile>-kohderyhmässä. Koska lxXlsSummary.pas-tiedostoa ei koskaan rekisteröity eksplisiittisesti projektitiedostoon, lxXlsSummary.obj-tiedostoa ei luoda. Kun linkittäjä yrittää ratkaista lxRead.obj-tiedoston tekemiä kutsuja, symbolit puuttuvat, mikä johtaa ratkaisemattomaan ulkoiseen virheeseen (unresolved external error)

Ulkoisten viitteiden ratkaiseminen Pragma Link -direktiivillä ja HPPEMIT-määrityksellä

Jos haluat varmistaa, että yksikkö on linkitetty oikein C++:ssa pakottamatta käyttäjää lisäämään .obj-tiedostoa manuaalisesti projektiinsa, voit käyttää Delphin {$HPPEMIT}-direktiiviä. Tämä käskee Delphi-kääntäjää syöttämään tietyn C++-ohjeen (#pragma link) luotuun .hpp-tiedostoon

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.

Kun C++Builder sisällyttää lxXlsSummary.hpp-tiedoston, kääntäjä kohtaa #pragma link -direktiivin ja käskee linkittäjää (ILINK32/ILINK64) automaattisesti ratkaisemaan symbolit tiedostosta lxXlsSummary.obj

Kultainen sääntö komponenttien ylläpitoon

Välttääksesi C++Builder-koontiversioiden täydellisen rikkoutumisen, sinun on otettava käyttöön tiukka rekisteröintikäytäntö. Aina kun kirjastoosi lisätään uusi Pascal-yksikkö, se on rekisteröitävä eksplisiittisesti kaikissa kolmessa projektitiedostotyypissä samanaikaisesti

1. Päivitä C++Builder-projekti (.cbproj / .bpk)

Avaa .cbproj-tiedosto tekstieditorissa ja lisää uusi yksikkö käännösluetteloon varmistaen, että annat sille yksilöllisen käännösjärjestyksen. Jos käytät vanhempia C++Builder-versioita .bpk-tiedostojen kanssa, varmista, että tunniste <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> on lisätty

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

2. Päivitä Delphi-paketti (.dpk)

Lisää yksikkö eksplisiittiseen contains-lausekkeeseen. Tämä varmistaa, ettei Delphi-kääntäjän tarvitse luottaa implisiittiseen linkitykseen, jota pidetään yleisesti huonona käytäntönä

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Jatkuvan integroinnin validointi

Lopullinen puolustuskeino tätä ansaa vastaan on CI/CD-validointi. Älä koskaan luota pelkästään onnistuneeseen Delphi-käännökseen ennen kaksikielisen komponentin julkaisemista. Koontikomentosarjojesi on kutsuttava MSBuild- tai bcc32c-komentorivityökaluja C++Builder-projekteille (esim. build-Win32-Lib-CB.cmd) ja suoritettava C++-kokeiluversion sekä täysien demojen täydellinen linkitys. Vasta kun C++-linkittäjä onnistuu, voit olla varma, että kaikki Delphi-yksiköt on rekisteröity oikein ja ne paljastavat symbolinsa C++-ajonajalle

Huomautus: Monialustainen kääntäjien yhteensopivuus ylläpidetään tiukasti HotXLS VCL -komponentin Delphi- ja C++Builder-versioiden välillä