HotPDF se kompiluje a běží pod Free Pascalem 3.2.2 s Lazarusem a poctivé shrnutí tohoto portu má dvě věty. Vytváření, načítání, ukládání, komprese, dekomprese, šifrování a dešifrování dokumentů — všechno funguje na backendech pouze v Pascalu, takže aplikace v Lazarusu může produkovat a konzumovat skutečné PDF bez jakékoli závislosti na C. Volitelné nativní obrazové kodeky ne, protože předkompilované objekty Win64 používají příchuť COFF, kterou nedokáže konzumovat ani jeden linker Free Pascalu, takže na tomto toolchainu se vstupní body rozřešou na stuby, které selhávají uzavřeně
Dostat se z „zkompiluje se“ na „funguje“ stálo specifickou sadu oprav a každá z nich je past, která najde jakoukoli jinou kódovou základnu Delphi přesouvající se na Free Pascal. Stojí za zapsání v pořadí, v jakém bolí
Proč zkompilovaná unita nic nedokazuje?
Protože Pascal unita může odkazovat na symbol, který nikdy nedělá nic užitečného, a přesto uspokojit kompilátor. V okamžiku, kdy všech 113 unit knihovny stavělo čistě pod Free Pascalem, obsluha archivních kontejnerů skutečně fungovala, ověřeno smoke testem, který otevřel CBZ a převedl jej do PDF. Zploštění XFA formulářů vůbec nefungovalo, protože zploštění musí nafouknout komprimovaný stream paketu /XFA a vstupní bod deflate byl stále stub. Nic ve výstupu sestavení nerozlišovalo tyto dva případy
Pravidlo z toho vzešlé je krátké. Než do poznámek k vydání napíšete, že funkce funguje na novém toolchainu, napište runtime sondu, která funkci procvičí na tomto toolchainu od začátku do konce. Pokrytí kompilací je předpoklad, nikdy důkaz. Širší obraz toho, co port pokrývá, je v poznámkách o podpoře Win64 ve Free Pascalu a Lazarusu
Výjimka uvnitř stubu cdecl se k volajícímu nedostane
Toto si zaslouží vlastní sekci, protože symptom je tak klamavý. Unita stubů vystavuje C vstupní body způsobem, jakým by je vystavovala statická knihovna, takže stub vypadá takto
// Vypadá rozumně. Není.
function inflate(Strm: Pointer; Flush: Integer): PtrUInt; cdecl;
public name 'inflate';
begin
raise ENotSupportedException.Create('codec unavailable');
end;
Ve Free Pascalu pro Win64 se tato výjimka k volajícímu nepropaguje. Neexistuje žádný handler try..except, který by ji viděl, protože unwinding přes hranici cdecl deklarovanou tímto způsobem nenesle Pascal rámec výjimky; proces se ukončí s exit kódem 217. Ze strany aplikace není žádná chyba, žádná zpráva ani logovací řádek, jen program, který zmizí. To je striktně horší než špatná odpověď, protože špatnou odpověď lze ošetřit
Lákavá oprava je donutit stub vracet namísto toho chybový kód a u inflate je to správně, protože zlib má dobře definovaný chybový návrat. Obecně je to špatně: stub pro jpeg_read_header vracející nulu řekne volajícímu, aby pokračoval se strukturou, kterou nikdo neinicializoval. Trvanlivá oprava je bránit na Pascal vstupním bodě namísto uvnitř stubu ve tvaru C a používat jakoukoli chybovou konvenci, kterou dané API už má
function TryDecodeJPEG(const Data: TBytes; out Bitmap: TBitmap): Boolean;
begin
{$IFDEF FPC}
// Odmítněte dříve, než je stub vůbec dosažen, s vlastní
// chybovou konvencí tohoto API namísto výjimky přes cdecl
Bitmap := nil;
Result := False;
Exit;
{$ENDIF}
Result := DecodeJPEGNative(Data, Bitmap);
end;
paszlib není zlib a rozdíl jsou dvě třídy dokumentů
Pascal implementace deflate dostupná ve Free Pascalu zvládá dvě rámování: obálku zlib a surový deflate. Nezvládá rámování gzip, které zlib volí hodnotami windowBits od 16 do 31, ani automatický detekční režim, který volí hodnoty 32 až 47. HotPDF potřebuje obojí. Bezpečná cesta importu SVG žádá 31 a loader má záchranný žebřík, který žádá 47, když je rámování streamu nejasné. Vynecháte-li kteréhokoli, přestane se otevírat celá rodina dokumentů s dekódovací chybou ukazující na stream, nikoli na chybějící rámování
Existuje druhá, ostřejší nekompatibilita. Záznam z_stream, který paszlib deklaruje, nemá stejné rozložení paměti jako C záznam: jeho pole msg je krátký řetězec, nikoli ukazatel, a total_in a total_out jsou 64bitové tam, kde C ABI má strojová slova. Záznam volajícího proto nelze předat přímo. Funkční uspořádání je udržovat stav paszlib za ukazatelem state, který veřejný záznam již rezervuje, a kopírovat veřejná pole dovnitř a ven kolem každého volání. CRC gzipu a osmibajtový length trailer jsou vysvětleny ve stejné vrstvě shimu, což je pro ně přirozené místo, protože už vlastní rozhodnutí o rámování
Předávání dynamického pole do netypového var parametru
To je chyba, která je nejspíše právě teď ve vašem kódu. Když předáte dynamické pole do netypového parametru var, to, co volaný obdrží, je adresa proměnné pole, což je adresa ukazatele, nikoli adresa dat. Čtení do něj tedy přepíše samotnou proměnnou a vše, co leží vedle ní
var
FBuffer: TBytes;
begin
SetLength(FBuffer, 65536);
// Špatně: předá adresu proměnné FBuffer
FStream.Read(FBuffer, Length(FBuffer));
// Správně: předá adresu prvního datového bajtu
FStream.Read(FBuffer[0], Length(FBuffer));
end;
V Delphi se špatná forma často zdá funkční, protože to, co poškodí, je přilehlý slot zásobníku, který si poté nic nepřečte. Ve Free Pascalu stejný řádek způsobí segmentation fault při prvním použití. To, co znesnadňuje odhalení okem, je, že statická pole takový problém nemají, protože proměnná statického pole je vlastními daty, takže oba zápisy jsou správné ve stejném souboru podle deklarace několik stovek řádků dále
Kontejnery ZIP bez System.Zip
Free Pascal nemá ekvivalent RTL unity pro zip a dostupná alternativa má jiný povrch API i bez podpory legacy šifrování, které starší formáty kontejnerů stále používají, takže malá interní čtečka knihovny vyšla kratší než přizpůsobení se jí. Dva detaily formátu stály čas a snadno se udělají špatně
První je kontrolní bajt šifrovací hlavičky. Její dvanáctý bajt je normálně vysoký bajt CRC, ale když je nastaven bit 3 obecného příznaku, což znamená, že velikosti žijí v koncovém data descriptoru a CRC ještě není známo, pochází kontrolní bajt namísto toho z vysokého bajtu času modifikace. Implementujete-li pouze formu CRC, každý archiv zapsaný v streamovacím režimu odmítne správné heslo. Druhé je extra pole ZIP64: jeho tři 64bitová pole se objevují v pevném pořadí, ale zapisují se pouze tehdy, když je odpovídající 32bitové pole nasycené, takže čtení na pevných ofsetech funguje na archivech, které jste testovali, a selže na dalším. Rozeberte je pozičně podle toho, která 32bitová pole jsou nasycená
Jedno pohodlí stojí za znalost: dekompresní stream Free Pascalu přijímá druhý argument konstruktoru, který přeskočí hlavičku zlib, což je přesně to, co položky ZIP potřebují, protože ukládají surový deflate. Tato cesta se knihovní zlib shim nedotkne vůbec, takže na ni chybějící C backend nemá vliv
Průhlednost barevných glyfů pod LCL
Čtení alfa kanálu rasterizovaného barevného glyfu je ten jediný grafický detail bez přímého převodu. Třída PNG v LCL nemá přístupovač scanline odhalující alfa a přiřazení PNG do bitmapy jej zahodí, takže barevné emoji dorazí zcela neprůhledné a skládá se s černou krabicí za sebou. Funkční cesta vede přes interface obrázek: vytvořte jej z PNG a poté čtěte pixely přes barevný přístupovač s vědomím, že jeho složky jsou 16bitové a potřebují posun o osm dolů, aby se staly bajty. Tento povrch také používá přirozené pořadí řádků shora dolů, takže inverze Height - 1 - Y, kterou potřebuje kód scanline ve VCL, musí být odstraněna, nikoli portována
Dvě poznámky k build systému, než zadáte chybu
Úplné znovusestavení občas selže s nedefinovaným symbolem, jehož název končí příponou $crc a hexadecimální hodnotou. Tato přípona se počítá z typů parametrů a neprojde shodou, když jedno sestavení kompiluje unitu proti dvěma různým verzím interface v témže průchodu. Opětovné spuštění sestavení to vyčistí; signatura není chybná
Za druhé, Free Pascal 3.2.2 nemá anonymní metody, takže všude, kde knihovna používala uzávěry k propojení paralelního potrubí, bere sestavení Free Pascalu deterministický sériový fallback. Výstup je identický, propustnost nikoli; pokud závisíte na paralelním vykreslování stránek, je to momentálně důvod zůstat u Delphi a návrh potrubí popisuje článek o paralelním renderovacím potrubí. Situace obrazových kodeků je druhým místem, kde volba toolchainu mění schopnosti, a ne pouze rychlost, takže nasazení Lazarusu by mělo plánovat své obrazové formy podle toho; aktuální matice podle toolchainů je na stránce produktu HotPDF Delphi PDF component