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
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}
// 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.
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
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
La verifica aggiornata considera sia ILINK32 sia ILINK64 e richiede che il progetto `.cbproj` o `.bpk` e il pacchetto `.dpk` includano esplicitamente le stesse unità, così gli external restano risolti anche in CI