Teknisk artikel

HotPDF på Free Pascal och Lazarus: Win64-stödets gränser

Det korta svaret på det supportärendet är ja, med gränser. HotPDF 2.730.0 bygger på Free Pascal 3.2.2 och Lazarus 4.6 för Win64, och kärnvägarna för att skapa, läsa in och spara fungerar. Det som inte följer med är allt som vilar på ett statiskt länkat nativt codec-objekt eller på Delphis anonyma metoder

Frågan kommer nästan alltid på samma sätt: ett team standardiserar på Lazarus för ett plattformsoberoende verktyg, eller ärver en Free Pascal-kodbas, och vill ha samma PDF-komponent som de redan licensierar för Delphi. Att portera ett moget Delphi-bibliotek är sällan en fråga om syntax. Det intressanta är vad porteringen blottlägger om var biblioteket i tysthet var kopplat till en enda verktygskedja, och i det här fallet sitter kopplingen på två mycket bestämda ställen: objektfilens ABI hos de medföljande codecerna, och kompilatorfunktionerna som gömmer sig bakom en versionssymbol

En kapacitetsmatris som jämför HotPDF-bygget för Delphi med bygget för Free Pascal 3.2.2 och Lazarus 4.6 på Win64, och visar vilka dokumentvägar som delas och vilka API:er för codec, komprimering, parallellrendering och anonyma metoder som når en stub som kastar
Kärnvägarna för att skapa, läsa in och spara är identiska i båda byggen, och gapet ligger helt i de statiskt länkade codecerna och API:erna med anonyma metoder

Vad Free Pascal 3.2.2 behöver innan HPDFDoc kompilerar

HotPDF kompilerar under Free Pascal bara i Delphi-läge, och bara när Lazarus LCL-enhetskataloger ligger på sökvägen. Inget av det är förhandlingsbart. HotPDF.inc ställer om kompilatorn med {$MODE DELPHI} och {$H+} inne i sitt {$IFDEF FPC}-block och vägrar allt äldre med ett {$FATAL} när FPC_FULLVERSION ligger under 30202, så en 3.0.x-installation havererar högljutt i stället för att producera en trasig enhet. Lazarus runtime-paket HotPDFLaz.lpk kodar in resten: LCL som ett obligatoriskt paket och -Mdelphi som ett eget kompilatoralternativ

LCL-kravet överraskar dem som bara vill ha konsolutdata, men det är strukturellt. HPDFFPCCompat levererar de Delphi-VCL-typer som Free Pascal saknar motsvarighet till: den mappar TMetafile och TMetafileCanvas på LCL:s bitmap- och canvasklasser och aliasar TRichEdit till TMemo, medan HPDFDoc aliasar TPNGObject till Graphics.TPortableNetworkGraphic. Behandla dem som shims för kompileringstid, inte som funktionsparitet: en metafilklass som backas av en bitmapp håller enheten kompilerbar, den får inte metafilvägarna att bete sig som de gör på Delphi. Till och med smoke-testet utan GUI drar in Interfaces, och byggskriptet skickar -Fu för lcl\units\x86_64-win64 och för utdatakatalogen för lazutils

Varför D2009+ inte kan tjäna som versionsgrind

Det är frestande att behandla Free Pascal-bygget som en modern kompilator och helt enkelt definiera den nyaste Delphi-funktionssymbolen. HotPDF gör inte det, och skälet är värt att säga rent ut: D2009+ betyder inte bara Unicode-strängar, det grindar också enheter vars publika API uttrycks med anonyma metoder. Free Pascal 3.2.2 stödjer varken Delphis anonyma metoder eller de API:erna, så att låna symbolen skulle dra in kod som inte går att kompilera. Uses-satsen i HPDFDoc bär därför två separata villkorliga svansar, och överlappningen mellan dem är avsiktlig snarare än en olyckshändelse

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

Varför stannar de nativa codecerna hos länkaren?

Därför att de är Win64-COFF-objekt som emitterats av en enda bestämd verktygskedja, och ingen av Free Pascals länkare på Win64 vill svälja dem: varken den interna länkaren eller vägen via extern GNU ld. Det är ett ABI-problem på objektfilsnivå, inte ett Pascal-problem, och ingen mängd villkorlig källkod löser det. Biblioteket väljer den enda ärliga väg som står till buds. Varje {$L}-direktiv som drar in ett statiskt codec-objekt är inlindat i {$IFNDEF FPC}, så Free Pascal-bygget utelämnar dem helt enkelt, och HPDFFPCCodecStubs levererar sedan varje saknad extern symbol som en stub som kastar i stället för att returnera

// 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 stubbtabellen är lång, och att läsa den talar om exakt vilka funktioner som är Delphi-exklusiva i dag: ingångspunkterna för deflate i zlib-ng och zopfli, komprimering och dekomprimering med libjpeg, JPEG 2000-codecen i OpenJPEG, libtiff med sina initierare per komprimeringsmetod, kodning och avkodning av JBIG2, ingångspunkterna för färgtransformer i Little-CMS samt AES-primitiverna. Designvalet bakom stubbarna betyder mer än listan. En saknad symbol vid länkningen ger dig en vägg av odefinierade referenser från en enhet du aldrig rört; en stub som kastar ENotSupportedException ger dig ett bygge som kör, ett meddelande som namnger orsaken och en stackspårning som pekar på anropsstället. Det betyder också att ett Free Pascal-bygge aldrig i tysthet producerar fel bytes där ett Delphi-bygge skulle producera rätt. Notera andraordningseffekten också: att köra obetrodda bildcodecer i en isolerad process är ett beslut som bara uppstår i Delphi-bygget, eftersom ett Free Pascal-bygge över huvud taget inte har någon nativ avkodare i processen att sandlådeisolera

På Delphi länkas HotPDF:s statiska codec-objekt in och kör nativt, medan Free Pascal-bygget för Win64 hoppar över länkdirektiven och dirigerar varje saknad extern symbol till en stub som kastar ett namngivet undantag på anropsstället
Att hoppa över länkdirektiven och stubba varje extern symbol förvandlar en vägg av odefinierade referenser till ett bygge som kör och namnger sina egna gränser

Komprimering: den första raden att ändra är cmNone

Innan du portar något annat, sätt Compression till cmNone. THPDFCompressionMethod erbjuder exakt två värden, cmNone och cmFlateDecode, och det andra går rakt in i de deflate-ingångspunkter som är stubbar i ett Free Pascal-bygge. Verifiera kärnobjektmodellen först med komprimeringen avstängd, och bestäm sedan vad du mer behöver. Det är den ordning som det medföljande smoke-testet använder: skapa ett okomprimerat dokument på en sida, läs in det igen och kontrollera att sidantalet kom tillbaka som ett. Okomprimerad utdata är större, och den är fortfarande en fullt giltig 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 en stubbad 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.

Vad händer med parallell sidrendering?

Den kompilerar fortfarande, returnerar fortfarande korrekta bitmappar och slutar vara parallell. THotPDF.RenderLoadedPagesParallel och THotPDF.RenderLoadedPagesParallelOrdered är byggda på TThread.CreateAnonymousThread med en inline-procedure som stängning, vilket Free Pascal 3.2.2 inte kan uttrycka, så Free Pascal-grenen kör ett deterministiskt seriellt reservspår: den går igenom sidindexen i ordning, anropar RenderLoadedPageToBitmap för varje och räknar de lyckade. API-formen, returvärdet och utdatavektorn är oförändrade, vilket är det som låter en enda kodbas byggas åt båda hållen

Samma anrop till parallell rendering i HotPDF kör på överlappande arbetartrådar under Delphi och går igenom sidindexen seriellt under Free Pascal, där pipelinens infopost rapporterar ett arbetarantal på ett i stället för att dölja reservspåret
Free Pascal-grenen behåller API-formen och utdatavektorn samtidigt som den rapporterar ett arbetarantal på ett, så kod som redan läser infoposten ser sanningen
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: Info.WorkerCount är vad minnesbudgeten tillät
  // Free Pascal: Info.WorkerCount är alltid 1, sidor i indexordning
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

Reservspåret är inte tyst, och det är den del som är värd att designa kring. Det fyller i THPDFParallelRenderPipelineInfo ärligt: PageCount från begäran, RequestedWorkerCount som ekar det du bad om, WorkerCount satt till 1, och räknarna för färdiga och levererade sidor matchar det som faktiskt kom tillbaka. Kod som redan inspekterar Info för att dimensionera en förloppsindikator eller en minnesbudget fortsätter att fungera och läser sanningen i stället för ett antagande. Om din genomströmningsplan hänger på den parallella renderingspipelinen och dess mottrycksmodell är den planen en Delphi-plan; på Free Pascal får du budgetera för den enkeltrådade kostnaden för att rendera en sida till en bitmapp multiplicerad med sidantalet

Vilket bygge bör du faktiskt leverera?

Välj efter kapacitet, inte efter preferens. Om ditt arbetsflöde är dokumentmontering, text- och vektorritning, ifyllning av formulär, inläsning och sparande, så täcker Free Pascal-bygget på Win64 det, och du bör validera med komprimeringen avstängd innan du slår på något. Om det handlar om bilder i JPEG, JPEG 2000, TIFF eller JBIG2, ICC-färgtransformer, komprimerad utdata eller genomströmning som hänger på många kärnor, håll dig till Delphi eller C++Builder tills vidare. Gränsen dras av ett objektfils-ABI och en saknad språkfunktion, som båda syns i källkoden i stället för att ligga begravda i en stödmatris, och som båda havererar med ett namngivet fel i stället för ett felaktigt resultat

Paketet för Free Pascal och Lazarus levereras i samma distribution som enheterna för Delphi och C++Builder, så en licens täcker båda och du kan testa Lazarus-vägen mot dina egna dokument innan du binder dig; produktsidan för HotPDF Delphi PDF Component bär den aktuella stödmatrisen för kompilatorer och den fullständiga API-referensen