HotPDF compileert en draait onder Free Pascal 3.2.2 met Lazarus, en de eerlijke samenvatting van die portering is twee zinnen lang. Documentcreatie, laden, opslaan, compressie, decompressie, versleuteling en ontsleuteling werken allemaal op backends van puur Pascal, dus een Lazarus-applicatie kan echte PDF produceren en verbruiken zonder enige C-afhankelijkheid. De optionele native beeldcodecs doen dat niet, want de voorgebouwde Win64-objecten gebruiken een COFF-variant die geen enkele Free Pascal-linker kan verwerken, dus op die toolchain lossen de toegangspunten op in stubs die veilig falen
Van "compileert" naar "werkt" komen vergde een specifieke set fixes, en elk daarvan is een valkuil die elke andere Delphi-codebase die naar Free Pascal verhuist zal vinden. Ze zijn het opschrijven waard, in de volgorde waarin ze pijn deden
Waarom bewijst het compileren van een unit niets?
Omdat een Pascal-unit naar een symbool kan verwijzen dat nooit iets nuttigs zal doen en toch aan de compiler voldoet. Op het moment dat alle 113 bibliotheekunits schoon bouwden onder Free Pascal, werkten de archive-containerhandlers echt, geverifieerd door een smoketest die een CBZ opende en naar PDF converteerde. XFA-formuliervlakmaking werkte helemaal niet, want vlakmaking moet de gecomprimeerde /XFA-packetstream inflaten en het deflate-toegangspunt was nog een stub. Niets in de builduitvoer onderscheidde die twee gevallen
De regel die daaruit volgde is kort. Schrijf, voordat u in een releasenotitie neerschrijft dat een functie op een nieuwe toolchain werkt, een runtime-probe die de functie end-to-end op die toolchain oefent. Compileerdekking is een voorwaarde, nooit bewijs. Het bredere beeld van wat de portering dekt staat in de supportnotities voor Free Pascal en Lazarus Win64
Een raise binnen een cdecl-stub bereikt de aanroeper niet
Deze verdient een eigen sectie omdat het symptoom zo misleidend is. De stub-units geven C-toegangspunten bloot zoals een statische bibliotheek dat zou doen, dus een stub ziet er zo uit
// Ziet er redelijk uit. Is het niet.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Onder Free Pascal voor Win64 propageert die exception niet naar de aanroeper. Er is geen try..except-handler die die ziet, want het afwikkelen over een cdecl-grens die op deze manier is gedeclareerd draagt het Pascal-exceptionframe niet mee; het proces eindigt met exitcode 217. Vanuit de applicatie is er geen fout, geen bericht en geen logregel, alleen een programma dat verdwijnt. Dat is strikt slechter dan een verkeerd antwoord, want een verkeerd antwoord kan worden afgehandeld
De verleidelijke oplossing is de stub een foutcode te laten teruggeven, en voor inflate klopt dat, omdat zlib een goed gedefinieerde foutreturn heeft. In het algemeen is het fout: een stub voor jpeg_read_header die nul teruggeeft zegt tegen de aanroeper door te gaan met een structuur die niemand heeft geïnitialiseerd. De duurzame oplossing is af te sluiten bij het Pascal-toegangspunt in plaats van binnen de C-vormige stub, met de foutconventie die die API al heeft
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Weiger voordat de stub ooit bereikt wordt, met de eigen
// foutconventie van deze API in plaats van een exception over cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib is niet zlib, en het verschil is twee documentklassen
De onder Free Pascal beschikbare Pascal-deflate-implementatie behandelt twee framings: de zlib-wrapper en raw deflate. Ze behandelt geen gzip-framing, die zlib selecteert via windowBits-waarden van 16 tot 31, en ze behandelt de automatische detectiemodus niet die door waarden 32 tot 47 wordt geselecteerd. HotPDF heeft beide nodig. Het veilige SVG-importpad vraagt om 31, en de loader heeft een fallback-ladder die om 47 vraagt wanneer de framing van een stream dubbelzinnig is. Er één overslaan en een hele familie documenten stopt met openen, met een decodeerfout die naar de stream wijst in plaats van naar de ontbrekende framing
Er is een tweede, scherpere incompatibiliteit. Het z_stream-record dat paszlib declareert heeft niet dezelfde geheugenlay-out als de C-variant: zijn veld msg is een shortstring in plaats van een pointer, en total_in en total_out zijn 64-bit waar de C ABI machinewoorden heeft. Een aanroepersrecord kan dus niet rechtstreeks worden doorgegeven. De werkende opzet is de paszlib-status achter de pointer state te houden die het publieke record al reserveert, en de publieke velden bij elke aanroep erin en eruit te kopiëren. De gzip-CRC en de lengtetrailer van acht bytes worden in diezelfde shim-laag verwerkt, wat de natuurlijke plek voor hen is omdat die laag al de framingbeslissing bezit
Een dynamische array aan een untyped var-parameter doorgeven
Dit is de bug die op dit moment waarschijnlijk in uw code zit. Wanneer u een dynamische array aan een untyped var-parameter doorgeeft, ontvangt de ontvanger het adres van de arrayvariabele, wat het adres van een pointer is, niet het adres van de payload. Een lezing erin overschrijft dus de variabele zelf en wat er ook naast zit
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Fout: geeft het adres van de variabele FBuffer door
FStream.Read(FBuffer, Length(FBuffer));
// Goed: geeft het adres van de eerste payloadbyte door
FStream.Read(FBuffer[0], Length(FBuffer));
end;
Onder Delphi lijkt de verkeerde vorm vaak te werken, omdat wat die corrumpeert een aangrenzende stackslot is dat daarna door niets wordt gelezen. Onder Free Pascal geeft dezelfde regel een segmentation fault bij eerste gebruik. Wat het zo moeilijk maakt om met het oog te spotten, is dat statische arrays dit probleem niet hebben, want een variabele van een statische array is zijn eigen payload, dus beide schrijfwijzen zijn correct in hetzelfde bestand, afhankelijk van de declaratie een paar honderd regels verderop
ZIP-containers zonder System.Zip
Free Pascal heeft geen equivalent van de RTL zip-unit, en het beschikbare alternatief heeft zowel een ander API-oppervlak als geen ondersteuning voor de legacy-versleuteling die oudere containerformaten nog gebruiken, dus een kleine lezer in de bibliotheek zelf bleek korter dan zich daaraan aanpassen. Twee formaatdetails kostten tijd en zijn makkelijk fout te doen
De eerste is de checkbyte van de encryption-header. Zijn twaalfde byte is normaal de hoge byte van de CRC, maar wanneer bit 3 van de general-purpose flag is gezet, wat betekent dat de groottes in een trailing data descriptor zitten en de CRC nog niet bekend is, komt de checkbyte in plaats daarvan uit de hoge byte van de wijzigingstijd. Alleen de CRC-vorm implementeren en elk archief dat in streamingmodus is weggeschreven verwerpt een correct wachtwoord. De tweede is het ZIP64 extra field: diens drie 64-bit velden verschijnen in een vaste volgorde maar worden alleen weggeschreven wanneer het overeenkomstige 32-bit veld verzadigd is, dus ze op vaste offsets lezen werkt op de archieven die u testte en faalt op de volgende. Parseer ze positioneel tegen welke 32-bit velden verzadigd zijn
Eén gemak dat het kennen waard is: de Free Pascal-decompressiestream neemt een tweede constructorargument mee dat de zlib-header overslaat, en dat is precies wat ZIP-items nodig hebben omdat ze raw deflate opslaan. Dat pad raakt de zlib-shim van de bibliotheek helemaal niet, dus het blijft onaangetast door de ontbrekende C-backend
Transparantie van kleurglyphs onder de LCL
Het uitlezen van het alfakanaal van een gerasterde kleurglyph is het ene graphicsdetail zonder directe vertaling. De PNG-klasse van de LCL heeft geen scanline-accessor die alfa blootgeeft, en een PNG aan een bitmap toewijzen wist het uit, dus een kleuremoji komt volledig dekkend aan en componeert met een zwart kader erachter. De werkende route is het interfacebeeld: maak het aan vanuit de PNG en lees daarna pixels via de kleuraccessor, in het achterhoofd houdend dat de componenten 16-bit zijn en acht posities naar beneden geschoven moeten worden om bytes te worden. Dat oppervlak gebruikt ook de natuurlijke top-down rijvolgorde, dus de inversie Height - 1 - Y die VCL-scanlinecode nodig heeft moet worden verwijderd in plaats van geporteerd
Twee buildsystemnotities voordat u een bug meldt
Een volledige herbouw faalt af en toe met een undefined symbol waarvan de naam eindigt op een $crc-suffix en een hexadecimale waarde. Die suffix wordt berekend uit de parametertypes en komt niet overeen wanneer één build een unit in dezelfde pas tegen twee verschillende interfaceversies compileert. De build opnieuw draaien lost het op; de signatuur klopt wel
Ten tweede heeft Free Pascal 3.2.2 geen anonieme methoden, dus overal waar de bibliotheek closures gebruikte om een parallelle pijplijn te bekabelen kiest de Free Pascal-build voor een deterministische seriële fallback in plaats daarvan. De uitvoer is identiek, de doorvoer niet; als u afhankelijk bent van parallel paginarenderen, is dat een reden om voorlopig bij Delphi te blijven, en het pijplijnontwerp wordt beschreven in het artikel over de parallelle renderpijplijn. De beeldcodecsituatie is de andere plek waar de keuze van de toolchain capaciteit verandert en niet alleen snelheid, dus een Lazarus-uitrol moet zijn beeldformaten dienovereenkomstig plannen; de huidige matrix per toolchain staat op de productpagina van de HotPDF Delphi PDF component