Odborný článok

HotPDF na Free Pascale a Lazare: limity podpory Win64

Krátky odpoveď na ten support ticket je áno, s limitmi. HotPDF 2.730.0 sa zostavuje na Free Pascale 3.2.2 a Lazare 4.6 pre Win64 a jadrové cesty vytvorenia, načítania a uloženia fungujú. Čo nenasleduje, je čokoľvek spočívajúce na staticky linkovanom natívnom kodekovom objekte alebo na Delphi anonymných metódach

Otázka zvyčajne prichádza rovnako: tím štandardizuje na Lazare pre cross-platformový nástroj, alebo zdedí Free Pascal kódex a chce ten istý PDF komponent, ktorý už licencujú pre Delphi. Portácia zrelej Delphi knižnice je zriedka záležitosť syntaxe. Zaujímavá časť je, čo portácia odhalí o tom, kde bola knižnica potichu spojená s jedným toolchainom, a v tomto prípade spojenie sedí na dvoch veľmi špecifických miestach: ABI objektových súborov priložených kodekov a kompilátorové vlastnosti skryté za verziovým symbolom

Matica schopností porovnávajúca HotPDF Delphi zostavu s Free Pascal 3.2.2 a Lazarus 4.6 Win64 zostavou, ukazujúca, ktoré dokumentové cesty sú zdieľané a ktoré kodekové, kompresné, paralelne renderujúce a anonymno-metódové API dosahujú stub, ktorý vyvolá výnimku
Jadrové cesty vytvorenia, načítania a uloženia sú identické na oboch zostavách a medzera sedí úplne v staticky linkovaných kodekoch a anonymno-metódových API

Čo Free Pascal 3.2.2 potrebuje, kým HPDFDoc skompiluje

HotPDF sa kompiluje pod Free Pascalom len v Delphi režime a len keď sú adresáre jednotiek Lazarus LCL na search path. Ani jedno sa nedá vyjednať. HotPDF.inc prepína kompilátor s {$MODE DELPHI} a {$H+} vo vnútri svojho bloku {$IFDEF FPC} a odmieta čokoľvek staršie s {$FATAL}, keď FPC_FULLVERSION je pod 30202, takže inštalácia 3.0.x zlyhá nahlas namiesto produkcie rozbitnej jednotky. Lazarus runtime balík HotPDFLaz.lpk kóduje zvyšok: LCL ako povinný balík a -Mdelphi ako vlastná voľba

Požiadavka LCL prekvapí ľudí, ktorí chcú len výstup do konzoly, ale je štrukturálna. HPDFFPCCompat dodáva Delphi VCL typy, pre ktoré Free Pascal nemá ekvivalent, mapujúc TMetafile a TMetafileCanvas na LCL bitmapové a canvas triedy a aliasujúc TRichEdit na TMemo, zatiaľ čo HPDFDoc aliasuje TPNGObject na Graphics.TPortableNetworkGraphic. Považujte tie za kompilačné shimy, nie paritu vlastností: trieda metafilu podložená bitmapou drží jednotku kompilujúcu, nerobí cesty metafilu správať sa tak, ako na Delphi. Aj non-GUI smoke test ťahá Interfaces a build skript posiela -Fu pre lcl\units\x86_64-win64 a výstupný adresár lazutils

Prečo D2009+ nemôže slúžiť ako verzióva brána

Je lákavé považovať Free Pascal zostavu za moderný kompilátor a jednoducho definovať najnovší symbol vlastností Delphi. HotPDF to nerobí a dôvod stojí za to povedať priamo: D2009+ neznamená len Unicode reťazce, taktiež bráni jednotkám, ktorých verejné API je vyjadrené anonymnými metódami. Free Pascal 3.2.2 nepodporuje ani Delphi anonymné metódy, ani tie API, takže požičanie symbolu by vtiahlo kód, ktorý sa nedá skompilovať. Uses klauzula HPDFDoc preto nesie dva samostatné kondicionálne chvosty a ich prekrytie je zámerne, nie náhodné

uses
  // ...
  HPDFJavaScript,
  HPDFFormCalcGraph
{$IFDEF FPC}
  , HPDFFPCCodecStubs,
  HPDFCMS,
  HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
  , HPDFXFARuntime,
  HPDFCMS,
  HPDFWinCertSigner,
  HPDFSignVerify,
  HPDFSignatureBatch
{$ENDIF};

Prečo sa natívne kodeky zastavia pri linkeri?

Pretože sú to Win64 COFF objekty emitované jedným konkrétnym toolchainom a ani jeden Free Pascal linker na Win64 ich nespotrebuje: ani interný linker, ani externá GNU ld cesta. Toto je problém ABI objektových súborov, nie Pascal problém a žiadne množstvo kondicionálneho zdroja to neopraví. Knižnica berie jedinú úprimnú cestu dostupnú. Každá direktíva {$L}, ktorá ťahá statický kodekový objekt, je zabalená v {$IFNDEF FPC}, takže Free Pascal zostava ich jednoducho vynechá a HPDFFPCCodecStubs potom dodá každý chýbajúci externý symbol ako stub, ktorý vyvolá výnimku namiesto vrátenia

// HPDFFPCCodecStubs.pas
function HPDFFPCNativeCodecUnavailable: PtrUInt;
begin
  raise ENotSupportedException.Create(
    'This native codec is not available in the Free Pascal build');
end;

function HPDFFPCStub_deflate: PtrUInt; cdecl;
  public name 'deflate';
begin
  Result := HPDFFPCNativeCodecUnavailable;
end;

Tá stubová tabuľka je dlhá a jej čítanie vám povie presne, ktoré schopnosti sú dnes Delphi-only: vstupné body deflate zlib-ng a zopfli, kompresia a dekompresia libjpeg, kodek OpenJPEG JPEG 2000, libtiff a jeho per-kompresné inicializátory, JBIG2 encode a decode, farebné transformačné vstupné body Little-CMS a AES primitívy. Dizajnová voľba za stubmi záleží viac než zoznam. Chýbajúci symbol v čase linku vám dá stenu nedefinovaných referencií z jednotky, ktorej ste sa nikdy nedotkli; stub, ktorý vyvolá ENotSupportedException, vám dá zostavu, ktorá beží, správu pomenuvajúcu dôvod a stack trace ukazujúci na miesto volania. Znamená to tiež, že Free Pascal zostava nikdy mlčky neprodukuje zlé bajty tam, kde by Delphi zostava produkovala správne. Všimnite si efekt druhého rádu tiež: behanie nedôveryhodných obrazových kodekov v izolovanom procese je rozhodnutie, ktoré vzniká len na Delphi zostave, pretože Free Pascal zostava nemá v procese natívny dekodér, ktorý by bolo treba sandboxovať

Na Delphi sa HotPDF statické kodekové objekty linkujú a bežia natívne, zatiaľ čo Free Pascal Win64 zostava preskočí linkovacie direktívy a smeruje každý chýbajúci externý symbol na stub, ktorý vyvolá pomenovanú výnimku na mieste volania
Preskočenie linkovacích direktív a stubovanie každého externého symbolu mení stenu nedefinovaných referencií na zostavu, ktorá beží a pomenuva vlastné limity

Kompresia: prvý riadok na zmenu je cmNone

Skôr než portujete čokoľvek iné, nastavte Compression na cmNone. THPDFCompressionMethod ponúka presne dve hodnoty, cmNone a cmFlateDecode a druhá smeruje rovno do vstupných bodov deflate, ktoré sú stubmi vo Free Pascal zostave. Overte najprv jadrový objektový model s kompresiou vypnutou, potom si rozhodnite, čo iné potrebujete. To je poradie, ktoré dodávaný smoke test používa: vytvoriť jednostranový nekomprimovaný dokument, znova ho načítať a asertovať, že počet strán sa vrátil ako jedna. Nekomprimovaný výstup je väčší a je to stále úplne platné PDF

program HotPDFLazarusSmoke;

{$mode delphi}
{$H+}

uses
  Interfaces, SysUtils, HPDFDoc;

var
  Pdf, Reloaded: THotPDF;
  OutputFile: string;
  PageCount: Integer;
begin
  OutputFile := IncludeTrailingPathDelimiter(GetTempDir) +
    'HotPDF-FPC-Smoke.pdf';
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := OutputFile;
    Pdf.Compression := cmNone;   // cmFlateDecode dosahuje stubovaný symbol
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(72, 72, 0, 'HotPDF Free Pascal smoke test');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;

  Reloaded := THotPDF.Create(nil);
  try
    PageCount := Reloaded.LoadFromFile(OutputFile);
    if PageCount <> 1 then
      raise Exception.CreateFmt('Expected one page, got %d', [PageCount]);
  finally
    Reloaded.Free;
  end;
end.

Čo sa stane s paralelným renderovaním strán?

Stále sa kompiluje, stále vracia správne bitmapy a prestáva byť paralelné. THotPDF.RenderLoadedPagesParallel a THotPDF.RenderLoadedPagesParallelOrdered sú postavené na TThread.CreateAnonymousThread s inline procedure uzáverom, ktorý Free Pascal 3.2.2 nedokáže vyjadriť, takže Free Pascal vetva beží deterministický sekvenčný fallback: prechádza indexy strán v poradí, volá RenderLoadedPageToBitmap pre každý a počíta úspechy. Tvar API, návratová hodnota a výstupné pole sú nezmenené, čo je to, čo dovoľuje jedinému kódexu stavať sa obojsmerne

Rovnaké paralelne renderovacie volanie HotPDF beží na prekrývajúcich sa robotníckych vláknach pod Delphi a prechádza indexy strán sekvenčne pod Free Pascal, s info záznamom pipeline hlásiacim počet robotníkov jeden namiesto skrývania fallbacku
Free Pascal vetva drží tvar API a výstupné pole pri hlásení počtu robotníkov jeden, takže kód, ktorý už číta info záznam, vidí pravdu
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: Info.WorkerCount je to, čo pamäťový rozpočet dovolil
  // Free Pascal: Info.WorkerCount je vždy 1, strany v poradí indexov
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

Fallback nie je tichý, čo je časť, ktorú stojí za to navrhnúť okolo. Napĺňa THPDFParallelRenderPipelineInfo úprimne: PageCount z žiadosti, RequestedWorkerCount odrážajúci to, čo ste požiadali, WorkerCount nastavený na 1 a dokončené a doručené počty zodpovedajúce tomu, čo skutočne prišlo späť. Kód, ktorý už inšpektuje Info na rozmery progres baru alebo pamäťového rozpočtu, funguje ďalej a číta pravdu namiesto predpokladu. Ak váš throughput plán závisí na paralelnej renderovacej pipeline a jej modeli backpressure, ten plán je Delphi plán; na Free Pascale rozpočítajte jednovláknové náklady renderovania strany na bitmapu vynásobené počtom strán

Ktorú zostavu by ste mali naozaj odoslať?

Vyberajte podľa schopností, nie podľa preferencie. Ak váš workflow je skladanie dokumentov, textové a vektorové kreslenie, plnenie formulárov, načítanie a uloženie, Free Pascal zostava na Win64 to pokrýva a mali by ste validovať s kompresiou vypnutou, než čokoľvek zapnete. Ak zahŕňa JPEG alebo JPEG 2000 alebo TIFF alebo JBIG2 obrázky, ICC farebné transformácie, komprimovaný výstup alebo throughput závislý na veľa jadrách, ostávajte zatiaľ na Delphi alebo C++Builder. Hranica je nakreslená ABI objektového súboru a chýbajúcou jazykovou vlastnosťou, obe viditeľné v zdroji namiesto zakopaných v support matici a obe zlyhávajú s pomenovanou chybou namiesto zlého výsledku

Free Pascal a Lazarus balík sa dodáva v tej istej distribúcii ako jednotky Delphi a C++Builder, takže licencia pokrýva oboje a môžete testovať Lazarus cestu proti vlastným dokumentom skôr, než sa ju zavážete; produktová stránka HotPDF Delphi PDF Component nesie aktuálnu maticu podpory kompilátorov a kompletnú API referenciu