Teknisk artikkel

HotPDF på Free Pascal og Lazarus: Win64-grensene

Det korte svaret på den støttesaken er ja, med grenser. HotPDF 2.730.0 bygges på Free Pascal 3.2.2 og Lazarus 4.6 for Win64, og kjernebanene for create, load og save virker. Det ikke følger med, er alt som hviler på et statisk linket innfødt kodek-objekt eller på Delphi-anonyme metoder

Spørsmålet ankommer vanligvis på samme måte: et team standardiserer på Lazarus for et tverrplattformverktøy, eller arver en Free Pascal-kodebase, og vil ha den samme PDF-komponenten de allerede lisensierer for Delphi. Å portere et modent Delphi-bibliotek er sjelden et spørsmål om syntaks. Den interessante delen er hva porten avslører om hvor biblioteket stille var koblet til én verktøykjede, og i dette tilfellet sitter koblingen på to helt konkrete steder: objektfil-ABI-en til de medfølgende kodekene, og kompilatorfunksjonene som gjemmer seg bak et versjonssymbol

En kapabilitetsmatrise som sammenligner HotPDF Delphi-bygget med Free Pascal 3.2.2- og Lazarus 4.6 Win64-bygget, og viser hvilke dokumentbaner som er delte og hvilke kodek-, komprimerings-, parallell gjengivelses- og anonym metode-API-er som når en utløsende stub
Kjernebanene for create, load og save er identiske i begge bygg, og gapet sitter helt i de statisk linkede kodekene og anonyme metode-API-ene

Hva Free Pascal 3.2.2 trenger før HPDFDoc kompilerer

HotPDF kompilerer under Free Pascal bare i Delphi-modus, og bare når Lazarus LCL-unit-katalogene er på søkestien. Ingen av delene er forhandlingsbar. HotPDF.inc bytter kompilatoren med {$MODE DELPHI} og {$H+} inne i sin {$IFDEF FPC}-blokk og nekter alt eldre med en {$FATAL} når FPC_FULLVERSION er under 30202, så en 3.0.x-installasjon feiler høyt i stedet for å produsere en ødelagt unit. Lazarus kjøretidspakken HotPDFLaz.lpk koder resten: LCL som påkrevd pakke og -Mdelphi som tilpasset alternativ

LCL-kravet overrasker folk som bare vil ha konsollutdata, men det er strukturelt. HPDFFPCCompat leverer Delphi VCL-typene Free Pascal ikke har noen ekvivalent for, ved å avbilde TMetafile og TMetafileCanvas på LCL bitmap- og canvas-klasser og aliasere TRichEdit til TMemo, mens HPDFDoc aliaserer TPNGObject til Graphics.TPortableNetworkGraphic. Behandle dem som kompileringstids-shims, ikke funksjonsparitet: en metafile-klasse støttet av en bitmap holder unit-en kompilerende, den får ikke metafile-banene til å oppføre seg slik de gjør på Delphi. Selv røyktesten uten GUI trekker inn Interfaces, og byggskriptet sender -Fu for lcl\units\x86_64-win64 og lazutils utdatakatalog

Hvorfor D2009+ ikke kan tjene som versjonsport

Det er fristende å behandle Free Pascal-bygget som en moderne kompilator og bare definere det nyeste Delphi-funksjonssymbolet. HotPDF gjør ikke det, og grunnen er verdt å si rett ut: D2009+ betyr ikke bare Unicode-strenger, den porter også unit-er hvis offentlige API uttrykkes med anonyme metoder. Free Pascal 3.2.2 støtter verken Delphi-anonyme metoder eller de API-ene, så å låne symbolet ville dra inn kode som ikke kan kompilere. Uses-klausulen i HPDFDoc bærer derfor to separate betingede haler, og overlappen mellom dem er villet snarere enn tilfeldig

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

Hvorfor stopper de innfødte kodekene ved linkeren?

Fordi de er Win64 COFF-objekter avgitt av én bestemt verktøykjede, og ingen av Free Pascal-linkerne på Win64 vil konsumere dem: verken den interne linkeren eller den eksterne GNU ld-banen. Dette er et objektfil-ABI-problem, ikke et Pascal-problem, og ingen mengde betinget kilde fikser det. Biblioteket tar den eneste ærlige ruten tilgjengelig. Hvert {$L}-direktiv som trekker inn et statisk kodek-objekt er pakket inn i {$IFNDEF FPC}, så Free Pascal-bygget utelater dem rett og slett, og HPDFFPCCodecStubs leverer deretter hvert manglende eksterne symbol som en stub som utløser unntak i stedet for å returnere

// 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;

Den stub-tabellen er lang, og å lese den forteller deg nøyaktig hvilke kapabiliteter som er Delphi-alene i dag: zlib-ng- og zopfli-deflate-inngangspunktene, libjpeg-komprimering og dekomprimering, OpenJPEG JPEG 2000-kodeken, libtiff og dets per-komprimering-initialiserere, JBIG2-koding og -dekoding, Little-CMS fargetransform-inngangspunktene, og AES-primitivene. Designvalget bak stubbene betyr mer enn listen. Et manglende symbol ved link-tid gir deg en vegg av udefinerte referanser fra en unit du aldri rørte; en stub som utløser ENotSupportedException gir deg et bygg som kjører, en melding som navngir grunnen, og et stacktrace som peker på kallstedet. Det betyr også at et Free Pascal-bygg aldri stille produserer feil byte der et Delphi-bygg ville produsert korrekte. Merk også andreordseffekten: kjøring av utruelige bildekodeker i en isolert prosess er et valg som bare oppstår på Delphi-bygget, for et Free Pascal-bygg har ingen innprosess innfødt dekoder å sandkasse i det hele tatt

På Delphi linkes HotPDF statiske kodek-objekter inn og kjøres innfødt, mens Free Pascal Win64-bygget hopper over link-direktivene og ruter hvert manglende eksterne symbol til en stub som utløser et navngitt unntak ved kallstedet
Å hoppe over link-direktivene og stubbe hvert eksterne symbol gjør en vegg av udefinerte referanser om til et bygg som kjører og navngir sine egne grenser

Komprimering: den første linjen å endre er cmNone

Før du porterer noe annet, sett Compression til cmNone. THPDFCompressionMethod tilbyr nøyaktig to verdier, cmNone og cmFlateDecode, og den andre ruter rett inn i deflate-inngangspunktene som er stubber i et Free Pascal-bygg. Verifiser kjernens objektmodell først med komprimering av, og avgjør så hva du ellers trenger. Det er rekkefølgen den medfølgende røyktesten bruker: opprett et ensides ukomprimert dokument, last det inn på nytt, og påstå at sideantallet kom tilbake som én. Ukomprimert utdata er større, og den er fremdeles en helt gyldig 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 når et stubbet 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.

Hva skjer med parallell sidengjengivelse?

Den kompilerer fortsatt, returnerer fortsatt korrekte bitmapser, og slutter å være parallell. THotPDF.RenderLoadedPagesParallel og THotPDF.RenderLoadedPagesParallelOrdered er bygget på TThread.CreateAnonymousThread med en inline procedure-lukking, som Free Pascal 3.2.2 ikke kan uttrykke, så Free Pascal-grenen kjører en deterministisk seriell reserve: den går sideindeksene i rekkefølge, kaller RenderLoadedPageToBitmap for hver, og teller suksessene. API-formen, returverdien og utdatamatrisen er uendret, noe som er det som lar én kodebase bygges begge veier

Det samme HotPDF parallelle gjengivelseskallet kjører på overlappende arbeidertråder under Delphi og går sideindeksene serielt under Free Pascal, med pipeline-informasjonsposten som rapporterer en arbeiderteller på én i stedet for å skjule reserven
Free Pascal-grenen beholder API-formen og utdatamatrisen mens den rapporterer en arbeiderteller på én, så kode som allerede leser informasjonsposten ser sannheten
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: Info.WorkerCount er hva minnebudsjettet tillot
  // Free Pascal: Info.WorkerCount er alltid 1, sider i indeksrekkefølge
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

Reserven er ikke stille, noe som er delen verdt å designe rundt. Den fyller THPDFParallelRenderPipelineInfo ærlig: PageCount fra forespørselen, RequestedWorkerCount som gjentar hva du ba om, WorkerCount satt til 1, og de fullførte og leverte tellerne som stemmer med det som faktisk kom tilbake. Kode som allerede inspekterer Info for å dimensjonere en fremdriftslinje eller et minnebudsjett fortsetter å virke og leser sannheten snarere enn en antagelse. Hvis gjennomstrømningsplanen din avhenger av den parallelle gjengivelsespipelinen og dens mottrykksmodell, er den planen en Delphi-plan; på Free Pascal, budsjetter for den entrådede kostnaden av å gjengi en side til en bitmap ganget med sideantallet

Hvilket bygg bør du faktisk levere?

Velg etter kapabilitet, ikke etter preferanse. Hvis arbeidsflyten din er dokumentsammenstilling, tekst- og vektortegning, skjemautfylling, innlasting og lagring, dekker Free Pascal-bygget på Win64 det, og du bør verifisere med komprimering av før du slår på noe som helst. Hvis den involverer JPEG-, JPEG 2000-, TIFF- eller JBIG2-bilder, ICC-fargetransformasjoner, komprimert utdata, eller gjennomstrømning som avhenger av mange kjerner, bli på Delphi eller C++Builder foreløpig. Grensen tegnes av en objektfil-ABI og en manglende språkfunksjon, som begge er synlige i kilden snarere enn begravd i en støttematrise, og som begge feiler med en navngitt feil snarere enn et feil resultat

Free Pascal- og Lazarus-pakken leveres i samme distribusjon som Delphi- og C++Builder-unit-ene, så en lisens dekker begge, og du kan teste Lazarus-banen mot dine egne dokumenter før du forplikter deg til den; HotPDF Delphi PDF Component-produktsiden fører den gjeldende kompilatorstøttematrisen og den fullstendige API-referansen