Delphi Linux64 følger LP64-datamodellen, så Longint og LongWord fylder 8 byte der, mens Delphi på Windows (Win32 og Win64) og Free Pascal på alle targets holder dem på 4. Porteringen af PDFlibPas, PDF-biblioteket til Delphi, til dcclinux64 rammer den forskel på tre tydelige måder plus en pointerbredde-fælde: en {$IFDEF WIN64}-test, der skulle vælge et pointerstort heltal, vælger i stilhed den forkerte bredde på alle 64-bit targets uden for Windows
Ingen af disse fælder er eksotisk. Hver af dem er en vane, som Windows gør sikker ved et tilfælde: at skrive Integer, hvor RTL'en siger Longint, at spejle en C-struktur med Longint-felter, eller at spørge "er det her Win64?", når det rigtige spørgsmål er "er en pointer 8 byte?". Kode kan gå rundt med de vaner i årevis uden konsekvenser, for på Win32, Win64 og FPC betyder de to stavemåder det samme. På Delphi Linux64 holder de op med at betyde det samme, og compileren er den første, der opdager det
Alt det nedenfor handler om det, compileren fanger. PDFlibrary.pas bygger med dcc32, dcc64 og dcclinux64 fra ét source tree, og det her er de problemer, der skiller rene Windows-vaner fra det tredje target
Hvad ændrer sig, når Delphi bygger til Linux64?
De heltalstyper, der ændrer bredde på Delphi Linux64, er præcis Longint og LongWord; Integer og Cardinal holder 4 byte på alle Delphi-platforme, og NativeInt følger pointeren. Windows er LLP64, hvor kun pointere vokser på 64-bit. Delphis 64-bit POSIX-targets (Linux64 plus macOS- og iOS-compilerne) er LP64, hvor C long også vokser, og Embarcadero lod Pascal Longint følge med
| Type | Delphi Win32 | Delphi Win64 | Delphi Linux64 | Free Pascal |
|---|---|---|---|---|
Integer / Cardinal | 4 | 4 | 4 | 4 |
Longint / LongWord | 4 | 4 | 8 | 4 på alle targets |
NativeInt / Pointer | 4 | 8 | 8 | pointerstørrelsen på targetet |
C long (platform ABI) | 4 | 4 | 8 | følger OS'ens ABI |
Kolonnen for Free Pascal er fælden for et cross-compiler-bibliotek. PDFlibPas leverer allerede en Linux-build via FPC, og FPC holder Longint på 32 bit på Linux x86-64. Så "det virker på Linux" var sandt og sagde os ingenting om Delphi Linux64. FPCs backend til ikke-Windows viste sig at være det rigtige fundament for Delphi-porteringen, men typenstørrelserne nedenunder er forskellige
Hvorfor trunkerer et WIN64-check pointere på Linux?
Det korte svar: WIN64 betyder "64-bit Windows", ikke "64-bit", og at behandle de to som synonymer vælger den forkerte heltalsbredde. PDFlibDither, en Floyd-Steinberg error diffusion-ditherer, der reducerer en 24-bit bitmap til få niveauer pr. kanal, går tre rækker af fejlværdier igennem med rå pointeraritmetik, og det pointerstorte heltal bag den aritmetik skal følge CPU'ens bredde, ikke operativsystemets. Den naive betingelse tager fejl:
// Naivt: bredden vælges af OS, så alle 64-bit targets uden for Windows får Integer
{$IFDEF WIN64}
Type
OSPtr= IntPtr;
{$ELSE}
Type
OSPtr= Integer;
{$ENDIF}
// Serpentine-scanningen rykker fejlrække-pointerne ét led ad gangen
Inc(OSPtr(ErrorR), SizeOf(TErrorTerm)); // FPC x86-64: overskriver kun de lave 32 bit
Dec(OSPtr(ErrorG), SizeOf(TErrorTerm)); // dcclinux64: E2064, venstre side kan ikke tildeles
// Korrekt: én pointerstor type overalt
Type
OSPtr= TPLIntPtr; // PDFlibTypes: NativeInt uden for Windows, IntPtr / Integer på Win64 / Win32
Med den betingelse bliver OSPtr til en 4-byte Integer på FPC for Linux x86-64, macOS x64 og ARM64, og Inc(OSPtr(ErrorR), ...) genfortolker de første 4 byte af en 8-byte pointer som et heltal og tæller dem op. På en little-endian-CPU er det den lave halvdel af adressen. Den høje halvdel røres aldrig, og derfor gemmer fejlen sig så godt: resultatet er korrekt lige til de lave 32 bit løber over, og på det tidspunkt hopper pointeren i stilhed 4 GiB tilbage. FPC kompilerer variabel-typecastet uden brok. Delphi nægter et variabel-typecast til en type af en anden størrelse på venstre side af en tildeling, så dcclinux64 stopper med E2064 ved det første Inc
Det er den nyttige asymmetri her: en compiler, der nægter typecastet, finder en pointerbredde-fejl, som en anden compiler accepterer i stilhed, før koden har kørt en eneste linje på det nye target. PDFlibPas bruger TPLIntPtr fra PDFlibTypes til det; den tester OS_WINDOWS først og skelner først derefter mellem Win64 og Win32, så ikke-Windows-targets altid får NativeInt
Den regel, vi tog med os: udled aldrig pointerbredde af et OS-define. Skal du bruge et pointerstort heltal, så brug NativeInt / NativeUInt (både Delphi og FPC har dem) eller et projektalias bygget på dem, og drop betingelsen helt. Har du reelt brug for at forgrene efter bredde i Delphi-kode, er CPU64BITS det define, der svarer på det spørgsmål, du faktisk stiller
TStream-overrides: kopiér RTL-signaturen, tegn for tegn
En TStream-efterkommer på Delphi Linux64 skal deklarere og implementere Read, Write og SetSize med Longint-parametre, præcis som RTL'en deklarerer dem, for Integer er ikke længere den samme type. PDFlibPas har sin egen TPDFlibWideFileStream til Unicode-filnavne, og dens klasseerklæring brugte faktisk Longint. Implementation-sektionen brugte Integer
Type
TPDFlibWideFileStream= Class(TStream)
Protected
Procedure SetSize(NewSize: Longint); Override;
Public
Function Read(Var Buffer; Count: Longint): Longint; Override;
Function Write(Const Buffer; Count: Longint): Longint; Override;
End;
// Før: accepteret i årevis på Win32, Win64 og FPC, hvor Integer = Longint
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Integer): Longint;
// Efter: implementation-headeren gentager erklæringen ordret
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Longint): Longint;
Begin
Result:= FileRead(FHandle, Buffer, Count);
If Result= - 1 Then
Result:= 0;
End;
På hver eneste compiler, biblioteket hidtil havde mødt, var Integer og Longint samme 4-byte-type, så en mismatchet header var umulig at skelne fra en matchende. På Delphi Linux64 matcher implementationen ikke længere sin egen erklæring, og unit'en stopper med at kompilere. Den her gør mere ondt, end dens størrelse antyder, for PDFlibExtra er en fundamental unit, som det meste af biblioteket afhænger af, så én mismatchet implementation-header forhindrer de fleste units i at kompilere
Samme resonnement virker den anden vej for ny kode. Når du overskriver en RTL-metode, så kopiér dens paramettyper ordret, selv når du "ved", at to typer er ens. Delphi Linux64 er simpelthen det første target, hvor den viden holder op med at passe
Hvilke records skal have Integer i stedet for Longint, og hvilke skal beholde den?
En record, der spejler et filformat eller en Windows-struktur, skal bruge fastbredde-typer (Integer, Cardinal, Int32), mens en record, der spejler en C-struktur deklareret med long, skal beholde Longint / LongWord. De to tilfælde ser ens ud i koden og kræver modsatte svar, for Windows LONG er altid 32 bit, og C long er, hvad platformens ABI nu siger
Type
// Uniscribe GOFFSET: to Windows LONG, altid 8 byte i alt
TGoffset= Packed Record
du: Integer; // var Longint: recorden voksede til 16 byte på Delphi Linux64
dv: Integer;
End;
// EMF LOGCOLORSPACE: header fyldt med en fast 68-byte Move fra record-data
TPLEMFLogColorSpace= Packed Record
Signature, Version, Size, ColorType, Intent: Cardinal;
Endpoints: Array[0..8] Of Integer; // var Longint: 36 byte blev 72
Gamma: Array[0..2] Of Cardinal;
Filename: Array[0..259] Of WideChar;
End;
// PDFium FPDF_FILEACCESS: m_FileLen er en C unsigned long, så LongWord er rigtigt
FPDF_FILEACCESS= Record
m_FileLen: LongWord; // 4 byte på Windows, 8 på LP64, samme som C-siden
m_GetBlock: TFPDF_GetBlock;
m_Param: Pointer;
End;
EMF-tilfældet viser, hvor lydløst det fejler. Importeren kopierer 68 byte ind i TPLEMFLogColorSpace: 20 byte header, 36 byte CIE-endpoints, 12 byte gamma. Med Longint-endpoints på LP64 ville de samme 68 byte lande i et 72-byte endpoint-array, gammaværdierne ville aldrig blive skrevet, og intet ville udløse en fejl. Begge records ligger på Windows-only-kodeveje og bruger fastbredde-typer alligevel, for en record, der spejler byte på disk, skal ikke afhænge af, hvilken compiler der nu tilfældigvis bygger den
PDFium-bindingen er modeksemplet. Dens m_FileLen spejler en C unsigned long, som er 32 bit på Windows og 64 bit på LP64, og LongWord følger den præcist begge steder. At omskrive den til Cardinal i en oprydningsrunde ville knække den, i samme sekund som PDFium-hosten fik en POSIX-loader. PDFium-hosten er Windows-only, så det er en note til fremtiden. Calling conventions i C-bindinger har deres egne fælder; det er emnet i artiklen om PDF-bibliotekets DLL-, ActiveX- og dylib-bindinger
En billig sikring for enhver record, der spejler byte: assert dens størrelse én gang ved opstart, for eksempel Assert(SizeOf(TGoffset) = 8) i en initialization-sektion. Det koster ingenting og forvandler en lydløs layoutændring til et øjeblikkeligt nedbrud, første gang et nyt target kører
Få de betingede defines rigtige: DELPHI_LINUX, Android og Types
Før nogle af typeproblemerne kunne dukke op, måtte Delphi på Linux holde op med at lade som om, den var Windows. PDFlibDefines.inc definerede slet ingenting for Delphi Linux, så compileren faldt direkte ned i Windows/VCL-defaults, og den første fejl var, at Vcl.Graphics ikke blev fundet. Definene fører Delphi Linux over på samme backend, som FPC bruger på ikke-Windows-targets: Cairo-rendering, fontconfig, OpenSSL og libcurl loadet på run time
{$IFDEF LINUX}
{$IFNDEF FPC}
{$IFNDEF ANDROID}
{$DEFINE DELPHI_LINUX}
{$DEFINE NOVCL}
{$DEFINE MD5_MAC}
{$DEFINE NOCRYPTAPI}
{$DEFINE USE_Cairo}
{$ENDIF}
{$ENDIF}
{$ENDIF}
To detaljer i den blok er lette at overse. ANDROID-udelukkelsen er der, fordi Delphi definerer LINUX for Android-targets også, og denne backend dækker ikke Android. Og det nye define er bevidst adskilt fra den eksisterende Delphi macOS-gren (OS_MAC uden FPC), som afhænger af Macapi-units; Linux-definet bygger i stedet på FPCs ikke-Windows-vej
Den anden tilbagevendende fejl var TPoint og TRect, der pludselig stod udeklareret. På Windows når de typer de fleste units indirekte, via Windows-unit'en. På Delphi Linux er der ingen Windows-unit i uses-klausulen, så units som PDFlibFont og PDFlibPageTree lister nu Types eksplicit under DELPHI_LINUX. Hvis din egen kode kun kompilerer på Windows, fordi Windows slæber System.Types med, så forvent samme mur. Det generelle mønster, kode der kun virker på én compiler ved et tilfælde, er emnet i fem Delphi-vaner, der kun virker ved et tilfælde, indtil FPC afslører dem
Hvor compileren holder op med at hjælpe
LP64-problemerne ovenfor er dem, en compiler kan finde. Dem, den ikke kan finde, for eksempel en record med forkert størrelse, der aldrig passerer et typecast, viser sig først, når testene faktisk kører på targetet — og det er præcis dét, de størrelses-asserts ovenfor er til
Hvis 1-bit-output er det, der bragte dig hen til dithering-koden, er den understøttede vej for renderede sider en anden, GDI-halftone-nedkonvertering uden for rendereren; den er beskrevet i rendering af PDF-sider til 1-bit monokrome bitmaps
Cross-compiler-arbejdet, der er beskrevet her, findes i PDF Library for Delphi, som i dag targetter Delphi og C++Builder på Windows plus headless Free Pascal-builds på Linux og macOS; productsiden lister de understøttede compilere og platforme i fuldt omfang