HotPDF compilează și rulează sub Free Pascal 3.2.2 cu Lazarus, iar rezumatul onest al acestei portări este de două propoziții. Crearea documentelor, încărcarea, salvarea, compresia, decompresia, criptarea și decriptarea funcționează toate pe backend-uri exclusiv Pascal, astfel încât o aplicație Lazarus poate produce și consuma PDF real fără nicio dependență C. Codecurile native opționale de imagini nu, deoarece obiectele Win64 precompilate folosesc o variantă COFF pe care niciun linker Free Pascal nu o poate consuma, deci pe acel toolchain punctele de intrare se rezolvă în stub-uri care eșuează închis
Drumul de la „compilează" la „funcționează" a luat un set specific de remedieri, iar fiecare dintre ele este o capcană care va prinde orice altă bază de cod Delphi care se mută pe Free Pascal. Merită notate în ordinea în care au durut
De ce nu dovedește nimic faptul că o unitate compilează?
Pentru că o unitate Pascal poate referenția un simbol care nu va face niciodată nimic util și tot satisface compilatorul. La momentul în care toate cele 113 unități ale bibliotecii se construiau curat sub Free Pascal, handlerii de containere de arhivă funcționau efectiv, verificați de un smoke test care deschidea un CBZ și îl convertea în PDF. Aplatizarea formularelor XFA nu funcționa deloc, deoarece aplatizarea trebuie să inflateze stream-ul de pachete /XFA comprimat, iar punctul de intrare deflate era încă un stub. Nimic din rezultatul build-ului nu distingea cele două cazuri
Regula rezultată este scurtă. Înainte de a scrie într-o notă de versiune că o funcție funcționează pe un toolchain nou, scrieți o sondă la execuție care exercită funcția de la un capăt la altul pe acel toolchain. Acoperirea la compilare este o premisă, niciodată o dovadă. Imaginea mai largă a ceea ce acoperă portarea este în notele de suport Free Pascal și Lazarus Win64
Un raise în interiorul unui stub cdecl nu ajunge la apelant
Acesta merită o secțiune proprie, deoarece simptomul este atât de înșelător. Unitățile stub expun puncte de intrare C așa cum ar face-o o bibliotecă statică, deci un stub arată așa
// Pare rezonabil. Nu este.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Pe Free Pascal pentru Win64 excepția aceea nu se propagă la apelant. Nu există niciun handler try..except care să o vadă, deoarece derularea peste o graniță cdecl declarată astfel nu transportă cadrul de excepție Pascal; procesul se termină cu cod de ieșire 217. Din partea aplicației nu există nicio eroare, niciun mesaj și nicio linie de jurnal, doar un program care dispare. Asta este strict mai rău decât un răspuns greșit, deoarece un răspuns greșit poate fi gestionat
Remedierea tentantă este să faceți stub-ul să returneze un cod de eșec, iar pentru inflate este corect, deoarece zlib are un retur de eroare bine definit. În general este greșit: un stub pentru jpeg_read_header care returnează zero îi spune apelantului să continue cu o structură pe care nimeni nu a inițializat-o. Remedierea durabilă este să filtrați la punctul de intrare Pascal, nu în interiorul stub-ului cu formă C, folosind orice convenție de eșec are deja API-ul respectiv
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Refuzați înainte ca stub-ul să fie atins vreodată, cu convenția
// de eșec proprie acestui API, nu cu o excepție peste cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib nu este zlib, iar diferența sunt două clase de documente
Implementarea Pascal deflate disponibilă pe Free Pascal gestionează două încadrări: învelișul zlib și deflate brut. Nu gestionează încadrarea gzip, pe care zlib o selectează prin valori windowBits de la 16 la 31, și nu gestionează modul de detecție automată pe care îl selectează valorile 32 până la 47. HotPDF are nevoie de ambele. Calea de import SVG sigură cere 31, iar loaderul are o scară de rezervă care cere 47 când încadrarea unui stream este ambiguă. Săriți peste oricare și o familie întreagă de documente încetează să se deschidă, cu o eroare de decodare care indică stream-ul, nu încadrarea lipsă
Există o a doua incompatibilitate, mai ascuțită. Recordul z_stream pe care paszlib îl declară nu are același aspect de memorie ca cel C: câmpul lui msg este un short string, nu un pointer, iar total_in și total_out sunt pe 64 de biți acolo unde ABI-ul C are cuvinte de mașină. Un record de apelant nu poate fi prin urmare trecut direct. Aranjamentul care funcționează este să păstrați starea paszlib în spatele pointerului state pe care recordul public îl rezervă deja, și să copiați câmpurile publice înainte și înapoi în jurul fiecărui apel. CRC-ul gzip și trailerul de lungime de opt octeți sunt contabilizate în același strat shim, care este locul natural pentru ele, deoarece deține deja decizia de încadrare
Transmiterea unui vector dinamic către un parametru var netipizat
Aceasta este eroarea cel mai probabil așezată chiar acum în codul dumneavoastră. Când transmiteți un vector dinamic către un parametru var netipizat, ceea ce primește apelatul este adresa variabilei vector, care este adresa unui pointer, nu adresa sarcinii utile. Deci o citire în ea suprascrie variabila în sine și orice se află lângă ea
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Greșit: predă adresa variabilei FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Corect: predă adresa primului octet de sarcină utilă
FStream.Read(FBuffer[0], Length(FBuffer));
end;
Pe Delphi forma greșită pare adesea că funcționează, deoarece ce corupe este o celulă de stivă adiacentă pe care nimic nu o citește după. Pe Free Pascal aceeași linie produce segmentation fault la prima folosire. Ce o face atât de greu de observat cu ochiul este că vectorii statici nu au o astfel de problemă, deoarece o variabilă de vector static este propria sa sarcină utilă, deci ambele scrieri sunt corecte în același fișier în funcție de declarația aflată la câteva sute de linii distanță
Containere ZIP fără System.Zip
Free Pascal nu are un echivalent al unității zip din RTL, iar alternativa disponibilă are atât o suprafață de API diferită, cât și lipsă de suport pentru criptarea legacy pe care formatele de containere mai vechi o folosesc încă, deci un mic cititor în interiorul bibliotecii s-a dovedit mai scurt decât adaptarea la ea. Două detalii de format au costat timp și sunt ușor de greșit
Primul este octetul de verificare al header-ului de criptare. Al său al doisprezecelea octet este de obicei octetul de sus al CRC-ului, dar când bitul 3 al fanionului de uz general este setat, adică mărimile trăiesc într-un descriptor de date de la final și CRC-ul nu este încă cunoscut, octetul de verificare vine din octetul de sus al timpului de modificare. Implementați doar forma cu CRC și fiecare arhivă scrisă în mod streaming respinge o parolă corectă. Al doilea este câmpul extra ZIP64: cele trei câmpuri pe 64 de biți apar într-o ordine fixă, dar sunt scrise doar când câmpul corespunzător pe 32 de biți este saturat, deci citirea lor la offset-uri fixe funcționează pe arhivele pe care le-ați testat și eșuează pe următoarea. Parsați-le pozițional în funcție de ce câmpuri pe 32 de biți sunt saturate
O conveniență care merită știută: stream-ul de decomprimare Free Pascal primește un al doilea argument de constructor care sare peste header-ul zlib, exact ceea ce au nevoie intrările ZIP, întrucât stochează deflate brut. Calea aceea nu atinge deloc shim-ul zlib al bibliotecii, deci nu este afectată de backend-ul C lipsă
Transparența glifelor color sub LCL
Citirea canalului alfa al unui glif color rasterizat este singurul detaliu grafic fără traducere directă. Clasa PNG din LCL nu are niciun accessor de scanline care să expună alfa, iar atribuirea unui PNG către un bitmap îl aruncă, deci un emoji color sosește complet opac și se compune cu o cutie neagră în spate. Calea care funcționează este imaginea de interfață: creați-o din PNG, apoi citiți pixelii prin accessorul de culoare, reținând că componentele lui sunt pe 16 biți și trebuie deplasate cu opt în jos ca să devină octeți. Suprafața aceea folosește și ordinea naturală de rând de sus în jos, deci inversarea Height - 1 - Y de care codul de scanline VCL are nevoie trebuie eliminată, nu portată
Două note de sistem de build înainte să raportați o eroare
O reconstrucție completă eșuează ocazional cu un simbol nedefinit al cărui nume se termină într-un sufix $crc și o valoare hexazecimală. Sufixul acela este calculat din tipurile de parametri și nu se potrivește când un build compilează o unitate împotriva a două versiuni diferite de interfață în aceeași trecere. Rularea din nou a build-ului o curăță; semnătura nu este greșită
În al doilea rând, Free Pascal 3.2.2 nu are metode anonime, deci oriunde biblioteca folosea closures pentru a conecta un pipeline paralel, build-ul Free Pascal ia în schimb o rezervă serială deterministă. Rezultatul este identic, debitul nu; dacă depindeți de randarea paralelă de pagini, acesta este un motiv să rămâneți pe Delphi pentru moment, iar designul pipeline-ului este descris în articolul despre pipeline-ul de randare paralelă. Situația codecurilor de imagini este celălalt loc în care alegerea toolchain-ului schimbă capacități, nu doar viteză, astfel încât o instalare Lazarus ar trebui să își planifice formatele de imagine în consecință; matricea actuală per toolchain este pe pagina de produs HotPDF Delphi PDF component