HotPDF kompilerer og kører under Free Pascal 3.2.2 med Lazarus, og den ærlige opsummering af den portering er to sætninger. Dokumentoprettelse, indlæsning, gemning, komprimering, dekomprimering, kryptering og dekryptering virker alle på rene Pascal-backends, så en Lazarus-applikation kan producere og indtage ægte PDF uden nogen C-afhængighed. De valgfrie native image codecs gør ikke, for de forbyggede Win64-objekter bruger en COFF-variant, som ingen af Free Pascal-linkerne kan indtage, så på den toolchain opløses entry points til stubs, der fejler lukket
At komme fra "kompilerer" til "virker" krævede et bestemt sæt rettelser, og hver eneste af dem er en fælde, der vil ramme enhver anden Delphi-kodebase, der flytter til Free Pascal. De er værd at nedskrive i den rækkefølge, de gør ondt
Hvorfor beviser en kompilerende unit ingenting?
Fordi en Pascal-unit kan referere et symbol, der aldrig vil gøre noget nyttigt, og stadig tilfredsstille compileren. Da alle 113 biblioteksunits byggede rent under Free Pascal, virkede arkivcontainerhåndteringerne genuint, verificeret af en smoke test, der åbnede en CBZ og konverterede den til PDF. XFA-formularfladning virkede slet ikke, for fladning skal inflate den komprimerede /XFA-pakkestrøm, og deflate-entry pointet var stadig en stub. Intet i build-outputtet skelnede de to tilfælde
Reglen, der kom ud af det, er kort. Før du skriver i en release note, at en funktion virker på en ny toolchain, skriv en runtime-probe, der afprøver funktionen ende til ende på den toolchain. Compile-dækning er en forudsætning, aldrig bevis. Det bredere billede af, hvad porteringen dækker, findes i noterne om Free Pascal- og Lazarus Win64-understøttelse
En raise inde i en cdecl-stub når ikke kalderen
Denne fortjener sin egen sektion, for symptomet er så vildledende. Stub-uniterne eksponerer C-entry points, som et statisk bibliotek ville, så en stub ser sådan ud
// Ser rimeligt ud. Er det ikke.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
På Free Pascal for Win64 propagerer den exception ikke til kalderen. Der er ingen try..except-handler, der ser den, for at unwinde hen over en sådan erklæret cdecl-grænse medfører ikke Pascal-exceptionframen; processen terminerer med exit code 217. Set fra applikationen er der ingen fejl, ingen besked og ingen loglinje, bare et program, der forsvinder. Det er strengt taget værre end et forkert svar, for et forkert svar kan håndteres
Den fristende rettelse er at få stubben til at returnere en fejlkode i stedet, og for inflate er det rigtigt, for zlib har en veldefineret fejlretur. Det er forkert generelt: en stub for jpeg_read_header, der returnerer nul, fortæller kalderen at fortsætte med en struktur, ingen har initialiseret. Den holdbare rettelse er at gate ved Pascal-entry pointet frem for inde i den C-formede stub under brug af den fejlkonvention, API'en allerede har
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Nægt, før stubben nogensinde nås, med dette API's egen
// fejlkonvention frem for en exception hen over cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib er ikke zlib, og forskellen er to dokumentklasser
Den Pascal-deflate-implementation, der findes til Free Pascal, håndterer to indramninger: zlib-wrapperen og rå deflate. Den håndterer ikke gzip-ramme, som zlib vælger gennem windowBits-værdier fra 16 til 31, og den håndterer ikke den automatiske detektionstilstand, som værdierne 32 til 47 vælger. HotPDF behøver begge. Den sikre SVG-importvej beder om 31, og loaderen har en fallback-stige, der beder om 47, når en strøms indramning er tvetydig. Spring én af dem over, og en hel familie af dokumenter holder op med at åbne, med en dekodefejl, der peger på strømmen frem for den manglende indramning
Der er en anden, skarpere inkompatibilitet. Den z_stream-record, som paszlib erklærer, har ikke det samme hukommelseslayout som C-udgaven: dens msg-felt er en short string frem for en pointer, og total_in og total_out er 64-bit, hvor C ABI har maskinord. En kalder-record kan derfor ikke sendes direkte igennem. Den arbejdende ordning er at holde paszlib-tilstanden bag den state-pointer, som den offentlige record allerede reserverer, og at kopiere de offentlige felter ind og ud omkring hvert kald. gzip-CRC'en og den otte byte lange længde-trailer håndteres i samme shim-lag, hvilket er det naturlige sted for dem, da det allerede ejer indramningsbeslutningen
At sende et dynamisk array til en utypet var-parameter
Dette er fejlen, der med størst sandsynlighed sidder i din kode lige nu. Når du sender et dynamisk array til en utypet var-parameter, modtager den kaldte adressen på arrayvariablen, hvilket er adressen på en pointer, ikke adressen på payloaden. Så en læsning ind i den overskriver selve variablen og alt, hvad der ligger ved siden af
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Forkert: overdrager adressen på FBuffer-variablen
FStream.Read(FBuffer, Length(FBuffer));
// Rigtigt: overdrager adressen på den første payload-byte
FStream.Read(FBuffer[0], Length(FBuffer));
end;
På Delphi ser den forkerte form ofte ud til at virke, for det, den korrumperer, er en tilstødende stack-plads, som intet læser bagefter. På Free Pascal giver samme linje en segmentation fault ved første brug. Det, der gør den så svær at få øje på, er, at statiske arrays ikke har det problem, da en statisk arrayvariabel er sin egen payload, så begge skrivemåder er korrekte i samme fil afhængigt af deklarationen et par hundrede linjer væk
ZIP-containere uden System.Zip
Free Pascal har ingen ækvivalent til RTL zip-uniten, og det tilgængelige alternativ har både en anden API-overflade og ingen understøttelse af den legacy-kryptering, som ældre containerformater stadig bruger, så en lille læser i biblioteket viste sig at være kortere end at tilpasse sig det. To formatdetaljer kostede tid og er lette at tage fejl af
Den første er krypteringsheaderens tjekbyte. Dens tolvte byte er normalt den høje byte af CRC'en, men når general-purpose flag bit 3 er sat, hvilket betyder, at størrelserne ligger i en efterfølgende data descriptor, og CRC'en endnu ikke er kendt, kommer tjekbytten fra den høje byte af ændringstidspunktet i stedet. Implementér kun CRC-formen, og hvert arkiv skrevet i streamingtilstand afviser en korrekt adgangskode. Den anden er ZIP64 extra-feltet: dets tre 64-bit-felter optræder i fast rækkefølge, men skrives kun, når det tilsvarende 32-bit-felt er mættet, så læsning af dem ved faste offsets virker på de arkiver, du testede, og fejler på det næste. Parse dem positionelt mod, hvilke 32-bit-felter der er mættede
En bekvemmelighed værd at kende: Free Pascal dekomprimeringsstrøm tager et andet konstruktorargument, der springer zlib-headeren over, hvilket er præcis, hvad ZIP-entries behøver, da de gemmer rå deflate. Den vej rører slet ikke bibliotekets zlib-shim, så den påvirkes ikke af den manglende C-backend
Farveglyph-gennemsigtighed under LCL
At læse alfakanalen i et rasteriseret farveglyph er den ene grafikdetalje uden direkte oversættelse. LCL PNG-klassen har ingen scanline-accessor, der eksponerer alpha, og at assigne en PNG til en bitmap smider den væk, så et farve-emoji ankommer fuldstændig opaqt og komponeres med en sort boks bag sig. Den virkende vej er interfacet image: skab det ud fra PNG'en, og læs derefter pixels gennem farve-accessoren og husk, at dets komponenter er 16-bit og behøver at skifte otte ned for at blive bytes. Den overflade bruger også naturlig top-down-rækkefølge, så Height - 1 - Y-inversionen, som VCL scanline-kode behøver, må fjernes frem for porteres
To build-system-noter, før du rapporterer en fejl
Et fuldt genbuild fejler af og til med et udefineret symbol, hvis navn slutter med et $crc-suffiks og en hex-værdi. Det suffiks beregnes ud fra parametertyperne, og det fejler at matche, når ét build kompilerer en unit mod to forskellige interfaceversioner i samme gennemløb. At køre buildet igen rydder det; signaturen er ikke forkert
For det andet har Free Pascal 3.2.2 ingen anonyme metoder, så alle de steder, hvor biblioteket brugte closures til at koble en parallel pipeline op, tager Free Pascal-bygget en deterministisk serial-fallback i stedet. Output er identisk, gennemløbshastighed er det ikke; hvis du er afhængig af parallel siderendering, er det en grund til at blive på Delphi foreløbigt, og pipeline-designet er beskrevet i artiklen om parallel render-pipeline. Image codec-situationen er det andet sted, hvor toolchain-valget ændrer evner frem for kun hastighed, så en Lazarus-udrulning bør planlægge sine billedformater derefter; den aktuelle matrix pr. toolchain findes på produktsiden HotPDF Delphi PDF component