Teknisk artikkel

C++Builder-kompatible Delphi Excel-komponenter

Når du vedlikeholder en VCL-komponent skrevet i Delphi, men brukt av både Delphi- og C++Builder-brukere, oppdager du raskt at de to byggesystemene behandler avhengigheter svært ulikt. En unit som kompilerer helt fint i Delphi, kan gi katastrofale lenkerfeil i C++Builder. Dette avviket er en hyppig felle for komponentforfattere, særlig når nye interne units legges til i et eksisterende bibliotek

Her er en detaljert gjennomgang av hvorfor dette skjer, med eksempler fra virkeligheten hentet fra Delphi-komponenten HotXLS, og av hvordan du gjør kryssbyggingen din skuddsikker med eksplisitte inkluderinger, {$HPPEMIT} og pragma-lenking

Fellen: implisitt kompilering kontra eksplisitt inkludering

Anta at du lager en ny Delphi-unit, lxXlsSummary.pas, for å håndtere dokumentmetadata, og at du legger den i uses i hovedtolkingsuniten din, lxRead.pas. Du trykker kompiler i Delphi-IDE-en, bygget blir grønt, og du sender ut oppdateringen

Dagen etter melder C++Builder-brukerne dine om en feil i lenkefasen: Unresolved external 'XlsReadSummaryInformation' referenced from lxRead.obj

Delphi-måten (dcc32)

Når Delphi-kompilatoren behandler en pakke (.dpk), ser den på de unitene som er eksplisitt listet i contains-klausulen. Hvis en av dem har en ekstern unit i uses (som lxXlsSummary.pas) som ikke står i contains-listen, utfører Delphi-kompilatoren implisitt statisk lenking. Den finner rett og slett .pas-filen i søkestien, kompilerer den til en .dcu og baker den inn i den ferdige .bpl-en. Bygget lykkes, og utelatelsen blir fullstendig maskert

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

Byggesystemet i C++Builder er langt strengere. Det genererer C++-objektfiler (.obj) og headere (.hpp) bare for de Delphi-unitene som er eksplisitt listet i <DelphiCompile>-elementgruppen i .cbproj-filen. Fordi lxXlsSummary.pas aldri ble eksplisitt registrert i prosjektfilen, opprettes ingen lxXlsSummary.obj. Når lenkeren prøver å løse opp kallene fra lxRead.obj, mangler symbolene, og resultatet er en feil om et uløst eksternt symbol

Sammenligning av byggeløypene for Delphi-komponenten HotXLS: Delphi dcc32 kompilerer implisitt en ulistet unit inn i pakken og bygger grønt, mens C++Builder MSBuild aldri genererer objektfilen dens og ILINK32 avbryter med et uløst eksternt symbol
Den samme ulistede uniten tar to ulike veier: dcc32 maskerer utelatelsen med implisitt statisk lenking, mens den strengere C++Builder-lenkeren avslører den

Løse eksterne symboler med pragma link og HPPEMIT

Vil du sikre at en unit blir riktig lenket i C++ uten å tvinge brukeren til å legge .obj-filen manuelt inn i prosjektet sitt, kan du bruke Delphis {$HPPEMIT}-direktiv. Det ber Delphi-kompilatoren om å sprøyte et bestemt C++-direktiv, #pragma link, inn i den genererte .hpp-filen

unit lxXlsSummary;

interface

{$IFDEF WINDOWS}
  // Sprøyt en pragma link inn i den genererte C++-headerfilen
  // Dette tvinger C++-lenkeren til å ta med den tilhørende .obj-filen
  {$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;
  // Logikk for uttrekk av metadata her
end;

end.

Når C++Builder inkluderer lxXlsSummary.hpp, møter kompilatoren #pragma link og ber automatisk lenkeren (ILINK32/ILINK64) om å løse symboler fra lxXlsSummary.obj

Flyt i fire kolonner for HPPEMIT-mekanismen med pragma link i Delphi: et deklarert direktiv får dcc32 til å sprøyte lenkerordren inn i lxXlsSummary.hpp, C++Builder tar den imot ved inkludering, og ILINK32 løser XlsReadSummaryInformation automatisk
Lenkerordren skrives én gang i Pascal-kilden, reiser inne i den genererte headeren, og når hvert framtidige C++-bygg uten manuelle prosjektendringer

Gullregelen for komponentvedlikehold

For å unngå å ødelegge C++Builder-bygg fullstendig må du innføre en streng registreringspolicy. Hver gang en ny Pascal-unit legges til i biblioteket ditt, må den registreres eksplisitt i alle tre prosjektfiltypene samtidig

1. Oppdater C++Builder-prosjektet (.cbproj / .bpk)

Åpne .cbproj-filen i en teksteditor og legg den nye uniten til i kompileringslisten, og pass på å gi den en unik byggerekkefølge. Bruker du eldre C++Builder-versjoner med .bpk-filer, må du sørge for at taggen <file containerid="PascalCompiler" designclass="" filename="lxXlsSummary.pas" formname="" localcommand="" unitname="lxXlsSummary"></file> legges til

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

2. Oppdater Delphi-pakken (.dpk)

Legg uniten til i den eksplisitte contains-klausulen. Da slipper Delphi-kompilatoren å lene seg på implisitt lenking, som uansett regnes 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 med kontinuerlig integrasjon

Det ytterste forsvaret mot denne fellen er validering i CI/CD. Stol aldri på et vellykket Delphi-bygg alene før du sender ut en komponent for to språk. Byggeskriptene dine må kalle MSBuild eller kommandolinjeverktøyene bcc32c på C++Builder-prosjektene (for eksempel build-Win32-Lib-CB.cmd) og kjøre en fullstendig lenking av C++-demoene, både prøveversjon og fullversjon. Først når C++-lenkeren lykkes, kan du være sikker på at alle Delphi-units er riktig registrert og eksponerer symbolene sine for C++-kjøretiden

Merk: Kompilatorkompatibilitet på tvers av plattformer opprettholdes strengt i både Delphi- og C++Builder-utgavene av HotXLS Delphi VCL Component