Teknisk artikel

Krydskompilering af Delphi-komponenter til C++Builder: Undgå Unresolved Externals

Når du vedligeholder en VCL-komponent skrevet i Delphi, men som bruges af både Delphi- og C++Builder-brugere, indser du hurtigt, at de to build-systemer behandler afhængigheder meget forskelligt. En unit, der kompilerer perfekt i Delphi, kan forårsage katastrofale linkerfejl i C++Builder. Denne uoverensstemmelse er en hyppig fælde for komponentforfattere, især når man tilføjer nye interne units til et eksisterende bibliotek

Her er en detaljeret gennemgang af, hvorfor dette sker, ved hjælp af eksempler fra den virkelige verden fra HotXLS-komponenten, og hvordan du skudsikrer din krydskompileringsproces ved at bruge eksplicitte includes, {$HPPEMIT} og pragma-linking

Fælden: Implicit kompilering vs. Eksplicit inklusion

Antag, at du opretter en ny Delphi-unit, lxXlsSummary.pas, til at håndtere dokumentmetadata, og du uses den i din primære parser-unit, lxRead.pas. Du trykker på compile i Delphi IDE, bygget bliver grønt, og du udsender opdateringen

Næste dag rapporterer dine C++Builder-brugere en fejl under linker-fasen: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Delphi-måden (dcc32)

Når Delphi-compileren behandler en pakke (.dpk), kigger den på de units, der udtrykkeligt er angivet i contains-klausulen. Hvis en af disse units uses en ekstern unit (som lxXlsSummary.pas), der ikke er i contains-listen, udfører Delphi-compileren implicit statisk linking. Den finder simpelthen .pas-filen i søgestien, kompilerer den til en .dcu og bager den ind i den resulterende .bpl. Bygget lykkes, hvilket fuldstændig skjuler udeladelsen

C++Builder-måden (MSBuild / .cbproj)

C++Builders build-system er meget strengere. Det genererer kun C++-objektfiler (.obj) og headers (.hpp) for de Delphi-units, der udtrykkeligt er angivet i <DelphiCompile>-elementgruppen i .cbproj-filen. Fordi lxXlsSummary.pas aldrig blev udtrykkeligt registreret i projektfilen, oprettes der ingen lxXlsSummary.obj. Når linkeren forsøger at løse de kald, der foretages af lxRead.obj, mangler symbolerne, hvilket fører til en unresolved external fejl

Løsning af Externals med Pragma Link og HPPEMIT

Hvis du vil sikre dig, at en unit linkes korrekt i C++ uden at tvinge brugeren til manuelt at tilføje .obj-filen til deres projekt, kan du bruge Delphis {$HPPEMIT}-direktiv. Dette fortæller Delphi-compileren at indskyde et specifikt C++ #pragma link-direktiv i den genererede .hpp-fil

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.

Når C++Builder inkluderer lxXlsSummary.hpp, støder compileren på #pragma link og fortæller automatisk linkeren (ILINK32/ILINK64) at den skal løse symboler fra lxXlsSummary.obj

Den gyldne regel for komponentvedligeholdelse

For helt at undgå at ødelægge C++Builder-builds skal du indføre en streng registreringspolitik. Når en ny Pascal-unit tilføjes til dit bibliotek, skal den udtrykkeligt registreres på tværs af alle tre projektfiltyper samtidigt

1. Opdater C++Builder-projektet (.cbproj / .bpk)

Åbn .cbproj-filen i en teksteditor og tilføj den nye unit til kompileringslisten, og sørg for at angive en unik build-rækkefølge. Hvis du bruger ældre C++Builder-versioner med .bpk-filer, skal du sikre dig, at <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> tagget er tilføjet

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

2. Opdater Delphi-pakken (.dpk)

Tilføj unitten til den eksplicitte contains-klausul. Dette sikrer, at Delphi-compileren ikke behøver at stole på implicit linking, hvilket generelt alligevel betragtes som dårlig praksis

package HotXLS;

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

requires
  rtl,
  vcl;

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

end.

Validering af Continuous Integration

Det ultimative forsvar mod denne fælde er CI/CD-validering. Stol aldrig kun på et vellykket Delphi-build, før du sender en tosproget komponent. Dine build-scripts skal påkalde MSBuild- eller bcc32c-kommandolinjeværktøjerne på C++Builder-projekterne (f.eks. build-Win32-Lib-CB.cmd) og køre et komplet link af C++-trial'en og de fulde demoer. Først når C++-linkeren lykkes, kan du være sikker på, at alle Delphi-units er korrekt registreret og eksponerer deres symboler for C++-runtimen

Bemærk: Tværplatformskompatibilitet for compiler opretholdes strengt på tværs af Delphi- og C++Builder-udgaverne af HotXLS VCL-komponenten