HotPDF sa kompiluje a beží pod Free Pascalom 3.2.2 s Lazarusom a úprimné zhrnutie tohto portu sú dve vety. Tvorba dokumentov, načítavanie, ukladanie, komprimácia, dekomprimácia, šifrovanie aj dešifrovanie fungujú na čisto pascaleských backendoch, takže aplikácia v Lazaruse dokáže produkovať a konzumovať skutočné PDF bez akejkoľvek C závislosti. Voliteľné natívne obrazové kodeky nie, pretože predpripravené Win64 objekty používajú variantu COFF, ktorú nedokáže skonzumovať ani jeden linker Free Pascalu, takže na tomto toolchainu sa vstupné body rozložia na stuby, ktoré zlyhávajú zatvorené
Dostať sa od „kompiluje sa“ k „funguje“ stálo špecifickú sadu opráv a každá z nich je pasca, ktorá nájde každú inú kódovú bázu Delphi smerujúcu do Free Pascalu. Stojí za to si ich zapísať v poradí, v akom boleli
Prečo kompilujúca sa jednotka nedokazuje nič?
Pretože pascaleská jednotka môže odkazovať na symbol, ktorý nikdy nespraví nič užitočné, a stále uspokojiť kompilátor. V momente, keď sa všetkých 113 jednotiek knižnice zostavilo čisto pod Free Pascalom, handlery archívnych kontajnerov naozaj fungovali, overené smoke testom, ktorý otvoril CBZ a previedol ho na PDF. Zrovnávanie XFA formulárov nefungovalo vôbec, pretože zrovnávanie musí nafúknuť komprimovaný stream paketov /XFA a vstupný bod deflate bol ešte stub. Nič vo výstupi buildu nerozoznalo tieto dva prípady
Pravidlo, ktoré z toho vzišlo, je krátke. Skôr než napíšete do poznámky k vydaniu, že funkcia funguje na novom toolchainu, napíšte runtime sondu, ktorá precvičí funkciu od začiatku do konca na tom toolchaine. Kompilačné pokrytie je predpoklad, nikdy dôkaz. Širší obraz toho, čo port pokrýva, je v poznámkach o podpore Free Pascal a Lazarus Win64
Výnimka vo vnútri cdecl stubu sa nedostane ku volajúcemu
Toto si zaslúži vlastnú sekciu, pretože príznak je tak zavádzajúci. Jednotky stubov vystavujú C vstupné body tak, ako by to robila statická knižnica, takže stub vyzerá takto
// Vyzerá rozumne. Nie je.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Na Free Pascale pre Win64 sa táto výnimka nešíri ku volajúcemu. Neexistuje handler try..except, ktorý by ju videl, pretože odvíjanie cez hranicu cdecl deklarovanú týmto spôsobom neprenáša pascaleský výnimkový rámec; proces sa ukončí s exit kódom 217. Zo strany aplikácie nie je žiadna chyba, žiadna správa a žiadny riadok logu, len program, ktorý zmizne. To je prísne horšie než nesprávna odpoveď, pretože nesprávna odpoveď sa dá spracovať
Lákavá oprava je prinútiť stub vraciať namiesto toho chybový kód a pri inflate je to správne, pretože zlib má dobre definovaný chybový návrat. Vo všeobecnosti je to zlé: stub pre jpeg_read_header, ktorý vracia nulu, hovorí volajúcemu, aby pokračoval so štruktúrou, ktorú neinicializoval nikto. Trvanlivá oprava je strážiť na pascaleskom vstupnom bode namiesto vnútra C-tvarového stubu, pričom sa použije akákoľvek konvencia zlyhania, ktorú dané API už má
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Odmietnuť skôr, než sa stub vôbec dosiahne, s vlastnou konvenciou
// zlyhania tohto API namiesto výnimky cez cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib nie je zlib a rozdiel sú dve triedy dokumentov
Pascaleská implementácia deflate dostupná vo Free Pascale spracováva dva rámce: obal zlib a surový deflate. Nespracováva rámec gzip, ktorý zlib vyberá cez hodnoty windowBits od 16 do 31, a nespracováva režim automatickej detekcie, ktorý vyberajú hodnoty 32 až 47. HotPDF potrebuje oboje. Bezpečná cesta importu SVG žiada 31 a loader má rebrík fallbackov, ktorý žiada 47, keď je rámec streamu nejasný. Vynecháte ktorýkoľvek z nich a celá rodina dokumentov prestane sa otvárať s dekódovacou chybou ukazujúcou na stream namiesto na chýbajúci rámec
Je tu druhá, ostrejšia nekompatibilita. Záznam z_stream, ktorý paszlib deklaruje, nemá rovnaké rozloženie pamäti ako C záznam: jeho pole msg je krátky reťazec namiesto ukazovateľa a total_in a total_out sú 64-bitové tam, kde C ABI má strojové slová. Volajúci záznam preto nemôže byť odovzdaný rovno. Funkčné usporiadanie je uchovať stav paszlib za ukazovateľom state, ktorý verejný záznam už rezervuje, a kopírovať verejné polia dnu a von okolo každého volania. CRC gzipu a osembajtová prípojka dĺžky sa riešia v tej istej vrstve shimu, čo je pre ne prirodzené miesto, keďže tá už vlastní rozhodnutie o rámci
Odovzdanie dynamického poľa netypovanému var parametru
Toto je chyba, ktorá je najpravdepodobnejšie práve teraz vo vašom kóde. Keď odovzdáte dynamické pole netypovanému parametru var, to, čo volaný prijíma, je adresa premennej poľa, čo je adresa ukazovateľa, nie adresa užitočného zaťaženia. Čítanie do neho teda prepíše samotnú premennú a všetko, čo sedí vedľa nej
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Zle: odovzdá adresu premennej FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Správne: odovzdá adresu prvého bajtu dát
FStream.Read(FBuffer[0], Length(FBuffer));
end;
V Delphi sa nesprávna forma často zdá fungovať, pretože to, čo poškodí, je susedný slot zásobníka, ktorý potom nič nečíta. Vo Free Pascale ten istý riadok spôsobí segmentation fault pri prvom použití. To, čo to robí tak ťažko spoznateľným na prvý pohľad, je, že statické polia nemajú takýto problém, keďže premenná statického poľa je vlastným užitočným zaťažením, takže oba zápisy sú správne v tom istom súbore v závislosti od deklarácie pár stoviek riadkov ďalej
Kontajnery ZIP bez System.Zip
Free Pascal nemá ekvivalent zip jednotky RTL a dostupná alternatíva má odlišný API povrch aj absenciu podpory legacy šifrovania, ktoré staršie formáty kontajnerov stále používajú, takže malý čítač priamo v knižnici vyšiel kratšie než prispôsobovanie sa jej. Dva detaily formátu stáli čas a dajú sa ľahko pokaziť
Prvý je kontrolný bajt hlavičky šifrovania. Jeho dvanásty bajt je normálne vysoký bajt CRC, ale keď je nastavený bit 3 všeobecného príznaku, čo znamená, že veľkosti bývajú v koncovom data descriptore a CRC ešte nie je známe, kontrolný bajt pochádza namiesto toho z vysokého bajtu času úpravy. Ak implementujete len formu CRC, každý archív zapísaný v streamovacom režime odmietne správne heslo. Druhý je extra pole ZIP64: jeho tri 64-bitové polia sa objavujú v pevnom poradí, ale zapisujú sa len vtedy, keď je zodpovedajúce 32-bitové pole nasýtené, takže čítanie ich na pevných offsetoch funguje na archívoch, ktoré ste testovali, a zlyhá na ďalšom. Parseujte ich pozične podľa toho, ktoré 32-bitové polia sú nasýtené
Jedno pohodlie, ktoré stojí za znalosť: dekompresný stream Free Pascalu berie druhý argument konštruktora, ktorý preskočí hlavičku zlib, čo je presne to, čo potrebujú položky ZIP, keďže uchovávajú surový deflate. Táto cesta sa knižničného zlib shimu vôbec nedotýka, takže nie je ovplyvnená chýbajúcim C backendom
Transparentnosť farebných glyfov pod LCL
Čítanie alpha kanála rasterizovaného farebného glyfu je ten jeden grafický detail bez priameho prekladu. Trieda PNG v LCL nemá prístup k scanline, ktorý vystavuje alpha, a priradenie PNG do bitmapy ju zahodí, takže farebné emoji prichádza úplne nepriehľadné a kompozituje sa s čiernym obdĺžnikom za sebou. Funkčná cesta je interface obraz: vytvorte ho z PNG, potom čítajte pixely cez farebný prístupník s tým, že jeho komponenty sú 16-bitové a potrebujú posun dolu o osem, aby sa stali bajtmi. Tento povrch tiež používa prirodzené poradie riadkov zhora nadol, takže inverzia Height - 1 - Y, ktorú potrebuje kód VCL scanline, sa musí odobrať, nie portovať
Dve poznámky k build systému, skôr než nahlásite chybu
Úplný rebuild občas zlyhá s nedefinovaným symbolom, ktorého meno končí príponou $crc a hex hodnotou. Tá prípona sa počíta z typov parametrov a prestane sedieť, keď jeden build skompiluje jednotku proti dvom rozdielnym verziám interface v tom istom priebehu. Znovu spustený build to vyčistí; signatúra nie je zlá
Zadruhé, Free Pascal 3.2.2 nemá anonymné metódy, takže všade, kde knižnica používala closure na zapojenie paralelnej pipeline, berie build Free Pascalu namiesto toho deterministický sériový fallback. Výstup je identický, priepustnosť nie; ak závisíte na paralelnom renderovaní stránok, je to dôvod zostať zatiaľ pri Delphi a návrh pipeline popisuje článok o paralelnej renderovacej pipeline. Situácia obrazových kodekov je druhé miesto, kde voľba toolchainu mení schopnosti, nielen rýchlosť, takže nasadenie Lazarus by malo naplánovať svoje obrazové formáty podľa toho; aktuálna matica podľa toolchainov je na produktovej stránke HotPDF Delphi PDF component