Articolo tecnico

Compilazione Incrociata di Componenti Delphi per C++Builder: Evitare External Irrisolti

Quando si mantiene un componente VCL scritto in Delphi ma utilizzato sia da utenti Delphi che da utenti C++Builder, ci si rende presto conto che i due sistemi di compilazione gestiscono le dipendenze in modo molto diverso. Un'unità che viene compilata perfettamente in Delphi potrebbe causare errori del linker catastrofici in C++Builder. Questa discrepanza è una trappola frequente per gli autori di componenti, in particolare quando si aggiungono nuove unità interne a una libreria esistente

Ecco un'analisi dettagliata del motivo per cui ciò accade, utilizzando esempi reali del componente HotXLS, e come rendere infallibile il processo di compilazione incrociata utilizzando inclusioni esplicite, {$HPPEMIT} e pragma linking

La Trappola: Compilazione Implicita vs. Inclusione Esplicita

Supponiamo di creare una nuova unità Delphi, lxXlsSummary.pas, per gestire i metadati dei documenti, e di utilizzare uses nell'unità di analisi principale, lxRead.pas. Premi compila nell'IDE Delphi, la build diventa verde e invii l'aggiornamento

Il giorno successivo, i tuoi utenti di C++Builder segnalano un errore durante la fase di collegamento: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Il Metodo Delphi (dcc32)

Quando il compilatore Delphi elabora un pacchetto (.dpk), analizza le unità elencate esplicitamente nella clausola contains. Se una di queste unità uses un'unità esterna (come lxXlsSummary.pas) che non è nell'elenco contains, il compilatore Delphi esegue il collegamento statico implicito. Trova semplicemente il file .pas nel percorso di ricerca, lo compila in un .dcu e lo inserisce nel .bpl risultante. La compilazione ha successo, mascherando completamente l'omissione

Il Metodo C++Builder (MSBuild / .cbproj)

Il sistema di compilazione di C++Builder è molto più rigoroso. Genera file oggetto C++ (.obj) e intestazioni (.hpp) solo per le unità Delphi esplicitamente elencate nel gruppo di elementi <DelphiCompile> del file .cbproj. Poiché lxXlsSummary.pas non è mai stato registrato in modo esplicito nel file di progetto, non viene creato alcun lxXlsSummary.obj. Quando il linker tenta di risolvere le chiamate fatte da lxRead.obj, i simboli sono mancanti, causando un errore di external non risolto

Confronto delle pipeline di build per il componente HotXLS Delphi: dcc32 di Delphi compila implicitamente una unit non elencata nel pacchetto e la build va a buon fine, mentre MSBuild di C++Builder non genera mai il suo file oggetto e ILINK32 abortisce con un unresolved external
La stessa unità non elencata segue due strade divergenti: dcc32 maschera l'omissione attraverso il linking statico implicito mentre il linker più severo di C++Builder la espone

Risolvere gli External con Pragma Link e HPPEMIT

Se si desidera garantire che un'unità sia collegata correttamente in C++ senza forzare l'utente ad aggiungere manualmente il file .obj al suo progetto, è possibile utilizzare la direttiva {$HPPEMIT} di Delphi. Questo dice al compilatore Delphi di inserire una specifica direttiva #pragma link del C++ nel file .hpp generato

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Inietta una pragma link nell'header C++ generato
  // Questo forza il linker C++ a includere il file .obj corrispondente
  {$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;
  // Logica di estrazione dei metadati qui
end;

end.

Quando C++Builder include lxXlsSummary.hpp, il compilatore incontra il #pragma link e dice automaticamente al linker (ILINK32/ILINK64) di risolvere i simboli da lxXlsSummary.obj

Flusso a quattro colonne del meccanismo di link del pragma HPPEMIT in Delphi: una direttiva dichiarata fa injectare a dcc32 l'ordine del linker in lxXlsSummary.hpp, C++Builder lo consuma all'include, e ILINK32 risolve XlsReadSummaryInformation automaticamente
L'ordine del linker viene scritto una volta nel sorgente Pascal, viaggia dentro l'header generato e raggiunge ogni futura build C++ senza modifiche manuali al progetto

La Regola d'Oro per la Manutenzione dei Componenti

Per evitare di interrompere del tutto le compilazioni di C++Builder, è necessario adottare una rigorosa politica di registrazione. Ogni volta che si aggiunge una nuova unità Pascal alla propria libreria, è necessario registrarla in modo esplicito in tutti e tre i tipi di file di progetto contemporaneamente

1. Aggiornare il Progetto C++Builder (.cbproj / .bpk)

Apri il file .cbproj in un editor di testo e aggiungi la nuova unità all'elenco di compilazione, assicurandoti di fornire un ordine di compilazione univoco. Se si utilizzano versioni precedenti di C++Builder con file .bpk, assicurarsi che sia aggiunto il tag <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file>

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

2. Aggiornare il Pacchetto Delphi (.dpk)

Aggiungi l'unità alla clausola esplicita contains. In questo modo si garantisce che il compilatore Delphi non debba fare affidamento sul collegamento implicito, che è in genere considerato una cattiva pratica in ogni caso

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Convalida dell'Integrazione Continua

La difesa finale contro questa trappola è la convalida CI/CD. Non fare mai affidamento esclusivamente su una compilazione Delphi riuscita prima di distribuire un componente bilingue. I tuoi script di compilazione devono richiamare gli strumenti da riga di comando MSBuild o bcc32c sui progetti C++Builder (ad esempio, build-Win32-Lib-CB.cmd) ed eseguire un collegamento completo della versione di prova in C++ e delle demo complete. Solo quando il linker C++ ha successo puoi essere certo che tutte le unità Delphi siano registrate correttamente ed espongano i loro simboli al runtime C++

Nota: la compatibilità del compilatore multipiattaforma è rigorosamente mantenuta in tutte le edizioni Delphi e C++Builder del Componente VCL HotXLS