Delphi Linux64 följer LP64-datamodellen, vilket betyder att Longint och LongWord är 8 byte där, medan Delphi på Windows (Win32 och Win64) och Free Pascal på alla mål håller dem på 4. När PDFlibPas, PDF-biblioteket för Delphi, ska portas till dcclinux64 krockar porten med den skillnaden på tre distinkta sätt, plus en pekbreddsfälla: ett {$IFDEF WIN64}-test som ska plocka fram ett heltal i pekarstorlek väljer i smyg fel bredd på alla 64-bitarsmål utanför Windows
Ingen av de här fällorna är exotisk. Var och en är en vana som Windows råkar göra ofarlig: att skriva Integer där RTL:n säger Longint, att spegla en C-struktur med Longint-fält, eller att fråga sig ”är detta Win64?” när den egentliga frågan är ”är en pekare 8 byte?”. Kod kan bära på de vanorna i åratal utan konsekvenser, eftersom de två stavningarna betyder samma sak på Win32, Win64 och FPC. På Delphi Linux64 slutar de betyda samma sak, och kompilatorn är den som märker det först
Allt nedan handlar om det kompilatorn fångar. PDFlibrary.pas byggs med dcc32, dcc64 och dcclinux64 från ett och samma kodträd, och det här är de problem som står mellan Windows-vanorna och det tredje målet
Vad ändras när Delphi siktar på Linux64?
De heltalstyper som ändrar bredd på Delphi Linux64 är precis Longint och LongWord; Integer och Cardinal står stilla på 4 byte på alla Delphi-plattformar, och NativeInt följer pekaren. Windows är LLP64, där bara pekarna växer på 64 bitar. Delphis 64-bitars POSIX-mål (Linux64 plus macOS- och iOS-kompilatorerna i 64 bitar) är LP64, där även C-typen long växer, och Embarcadero lät Pascals Longint följa med
| Typ | Delphi Win32 | Delphi Win64 | Delphi Linux64 | Free Pascal |
|---|---|---|---|---|
Integer / Cardinal | 4 | 4 | 4 | 4 |
Longint / LongWord | 4 | 4 | 8 | 4 på alla mål |
NativeInt / Pointer | 4 | 8 | 8 | målets pekstorlek |
C long (plattforms-ABI) | 4 | 4 | 8 | följer operativsystemets ABI |
Free Pascal-kolumnen är fällan för ett bibliotek som ska byggas med flera kompilatorer. PDFlibPas levererar redan en Linux-build via FPC, och FPC håller Longint på 32 bitar på Linux x86-64. Så ”det funkar på Linux” var sant, men berättade ingenting om Delphi Linux64. FPC:s backend utanför Windows visade sig vara rätt grund för Delphi-porten, men typstorlekarna under huven skiljer sig
Varför trunkerar en WIN64-test pekare på Linux?
Det korta svaret: WIN64 betyder ”64-bitars Windows”, inte ”64 bitar”, och att behandla de två som synonymer plockar fel heltalsbredd. PDFlibDither, en Floyd-Steinberg-ditherare med error diffusion som pressar en 24-bitars bitmap till några nivåer per kanal, går igenom tre rader av feltermer med rå pekararitmetik, och heltalen i pekarstorlek bakom den aritmetiken måste följa CPU:ns bredd, inte operativsystemets. Det naiva villkoret får det fel:
// Naivt: bredden väljs av OS, så alla 64-bitarsmål utanför Windows får Integer
{$IFDEF WIN64}
Type
OSPtr= IntPtr;
{$ELSE}
Type
OSPtr= Integer;
{$ENDIF}
// Den serpentinformade skanningen stegar felradernas pekare en term i taget
Inc(OSPtr(ErrorR), SizeOf(TErrorTerm)); // FPC x86-64: skriver bara om de låga 32 bitarna
Dec(OSPtr(ErrorG), SizeOf(TErrorTerm)); // dcclinux64: E2064, vänsterledet kan inte tilldelas
// Korrekt: en typ i pekarstorlek överallt
Type
OSPtr= TPLIntPtr; // PDFlibTypes: NativeInt utanför Windows, IntPtr / Integer på Win64 / Win32
Med det villkoret löses OSPtr till en 4-byte Integer på FPC för Linux x86-64, macOS x64 och ARM64, och Inc(OSPtr(ErrorR), ...) tolkar om de första 4 bytena i en 8-byte pekare som ett heltal och ökar dem. På en little-endian-CPU är det den låga halvan av adressen. Den höga halvan rörds aldrig, och det är därför misstaget gömmer sig så bra: resultatet stämmer ända tills de låga 32 bitarna rullar över, och i det ögonblicket hoppar pekaren tyst tillbaka 4 GiB. FPC kompilerar variabelcasten utan klagan. Delphi vägrar casta en variabel till en typ med annan storlek på vänster sidan om en tilldelning, så dcclinux64 stannar på E2064 vid första Inc
Det är den användbara asymmetrin här: en kompilator som vägrar casten hittar ett pekbreddsmisstag som en annan kompilator accepterar i tysthet, innan koden kört en enda rad på det nya målet. PDFlibPas använder TPLIntPtr från PDFlibTypes för det, som testar OS_WINDOWS först och först därefter skiljer Win64 från Win32, så mål utanför Windows får alltid NativeInt
Den regeln vi tog med oss: gissa aldrig pekbredd från ett OS-define. Behöver du ett heltal i pekarstorlek, använd NativeInt / NativeUInt (både Delphi och FPC har dem) eller ett projektalias byggt på dem, och hoppa över villkoret helt. Behöver du på riktigt förgrena på bredd i Delphi-kod är CPU64BITS det define som svarar på frågan du faktiskt ställer
TStream-override: kopiera RTL-signaturen, tecken för tecken
En TStream-arvinge på Delphi Linux64 måste deklarera och implementera Read, Write och SetSize med Longint-parametrar, exakt som RTL:n deklarerar dem, för Integer inte längre är samma typ. PDFlibPas har en egen TPDFlibWideFileStream för Unicode-filnamn, och dess klassdeklaration använde faktiskt Longint. Implementationsdelen använde 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örr: accepterat i åratal på Win32, Win64 och FPC, där Integer = Longint
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Integer): Longint;
// Efter: implementationshuvudet upprepar deklarationen exakt
Function TPDFlibWideFileStream.Read(Var Buffer; Count: Longint): Longint;
Begin
Result:= FileRead(FHandle, Buffer, Count);
If Result= - 1 Then
Result:= 0;
End;
På alla kompilatorer biblioteket någonsin mött var Integer och Longint samma 4-byte-typ, så ett felmatchat huvud var omöjligt att skilja från ett korrekt. På Delphi Linux64 matchar inte längre implementationen sin egen deklaration, och uniten slutar kompileras. Den här gör ont mer än sin storlek antyder, eftersom PDFlibExtra är en basunit som största delen av biblioteket står och faller med, så ett enda felmatchat implementationshuvud hindrar de flesta uniterna från att kompilera
Samma resonemang går åt andra hållet för ny kod. När du overridar en RTL-metod, kopiera dess parametertyper rakt av, även när du ”vet” att två typer är samma sak. Delphi Linux64 är bara det första målet där det vetandet slutar stämma
Vilka records behöver Integer i stället för Longint, och vilka måste behålla den?
En record som speglar ett filformat eller en Windows-struktur måste använda typer med fast bredd (Integer, Cardinal, Int32), medan en record som speglar en C-struktur deklarerad med long bör behålla Longint / LongWord. De två fallen ser identiska ut i källkoden och kräver motsatta svar, eftersom Windows LONG alltid är 32 bitar och C long är vad plattforms-ABI:n säger
Type
// Uniscribe GOFFSET: två Windows LONG, alltid 8 byte totalt
TGoffset= Packed Record
du: Integer; // var Longint: recorden växte till 16 byte på Delphi Linux64
dv: Integer;
End;
// EMF LOGCOLORSPACE: huvud fyllt med en fast 68-byte Move från recorddata
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 är en C unsigned long, så LongWord är rätt
FPDF_FILEACCESS= Record
m_FileLen: LongWord; // 4 byte på Windows, 8 på LP64, samma som C-sidan
m_GetBlock: TFPDF_GetBlock;
m_Param: Pointer;
End;
EMF-fallet visar hur tyst det här går snett. Importeraren kopierar 68 byte till TPLEMFLogColorSpace: 20 byte huvud, 36 byte CIE-endpoints, 12 byte gamma. Med Longint-endpoints på LP64 skulle samma 68 byte hamna i en 72-byte endpoint-array, gammavärdena skulle aldrig skrivas, och ingenting skulle kasta ett fel. Båda records sitter på kodvägar som bara finns på Windows, och de använder ändå typer med fast bredd, för en record som speglar byte på disk ska inte bero på vilken kompilator som råkar bygga den
PDFium-bindningen är motexemplet. Dess m_FileLen speglar en C unsigned long, som är 32 bitar på Windows och 64 bitar på LP64, och LongWord följer det exakt på båda. Att skriva om den till Cardinal i en städpassning skulle krossa den i samma stund PDFium-värden får en POSIX-laddare. PDFium-värden är Windows-only, så det här är en anteckning för framtiden. Anropskonventionssidan hos C-bindningar har fällor av egen art, beskrivna i artikeln om PDF-bibliotekets DLL-, ActiveX- och dylib-bindningar
Ett billigt skydd för varje record som speglar byte: asserta dess storlek en gång vid uppstart, till exempel Assert(SizeOf(TGoffset) = 8) i en initialization-sektion. Det kostar ingenting och förvandlar en tyst layoutändring till ett omedelbart fel vid första körningen på ett nytt mål
Rätt conditional defines: DELPHI_LINUX, Android och Types
Innan någon av typfrågorna kunde visa sig fick Delphi på Linux sluta låtsas vara Windows. PDFlibDefines.inc definierade ingenting alls för Delphi Linux, så kompilatorn trampade rakt ner i Windows-/VCL-standarderna, och det första felet var att Vcl.Graphics inte hittades. Definena routar Delphi Linux till samma backend som FPC använder på mål utanför Windows: Cairo-rendering, fontconfig, OpenSSL och libcurl laddade vid körning
{$IFDEF LINUX}
{$IFNDEF FPC}
{$IFNDEF ANDROID}
{$DEFINE DELPHI_LINUX}
{$DEFINE NOVCL}
{$DEFINE MD5_MAC}
{$DEFINE NOCRYPTAPI}
{$DEFINE USE_Cairo}
{$ENDIF}
{$ENDIF}
{$ENDIF}
Två detaljer i det blocket är lätta att missa. ANDROID-uteslutningen finns där eftersom Delphi definierar LINUX för Android-mål också, och den här backenden täcker inte Android. Och det nya definet hålls medvetet isär från den befintliga Delphi-grenen för macOS (OS_MAC utan FPC), som bygger på Macapi-uniter; Linux-definet bygger i stället på FPC:s väg utanför Windows
Det andra återkommande felet var TPoint och TRect som inte var deklarerade. På Windows når de typerna de flesta uniter indirekt, via Windows-uniten. På Delphi Linux finns ingen Windows-unit i uses-satsen, så uniter som PDFlibFont och PDFlibPageTree listar nu Types explicit under DELPHI_LINUX. Om din egen kod kompilerar på Windows bara för att Windows släpar med sig System.Types, räkna med samma mur. Det allmänna mönstret hos kod som bara funkar på en kompilator av ren tur är ämnet för fem Delphi-vanor som bara funkar av tur tills FPC avslöjar dem
Där kompilatorn slutar hjälpa
LP64-problemen ovan är de som en kompilator kan hitta. De den inte hittar, som en record med fel storlek som aldrig passerar en typcast, visar sig först när testerna faktiskt körs på målet, och det är precis vad storleksassertionerna ovan är till för
Om 1-bitars utdata var det som ledde dig till ditheringkoden från början är den supportade vägen för renderade sidor en annan: GDI-halftone-nedkonvertering utanför renderaren, beskriven i att rendera PDF-sidor till 1-bitars monokroma bitmaps
Grundarbetet för flera kompilatorer som beskrivs här finns i PDF Library for Delphi, som idag riktar sig mot Delphi och C++Builder på Windows plus headless Free Pascal-builds på Linux och macOS; produktsidan listar alla stödda kompilatorer och plattformar