Die kurze Antwort auf dieses Support-Ticket lautet ja, mit Grenzen. HotPDF 2.730.0 baut unter Free Pascal 3.2.2 und Lazarus 4.6 für Win64, und die Kernpfade für Erzeugen, Laden und Speichern funktionieren. Was nicht mitkommt, ist alles, was auf einem statisch gelinkten nativen Codec-Objekt oder auf Delphi-anonymen Methoden ruht
Die Frage kommt meist auf dieselbe Art: Ein Team stellt auf Lazarus um für ein plattformübergreifendes Werkzeug, oder es erbt eine Free-Pascal-Codebasis und will dieselbe PDF-Komponente, die es für Delphi bereits lizenziert hat. Eine ausgereifte Delphi-Bibliothek zu portieren ist selten eine Syntaxfrage. Der interessante Teil ist, was die Portierung darüber verrät, wo die Bibliothek still an eine Toolchain gekoppelt war, und hier sitzt die Kopplung an zwei sehr konkreten Stellen: am Objektdatei-ABI der mitgelieferten Codecs und an den Compiler-Features hinter einem Versionssymbol
Was Free Pascal 3.2.2 braucht, bevor HPDFDoc kompiliert
HotPDF kompiliert unter Free Pascal nur im Delphi-Modus und nur, wenn die Lazarus-LCL-Unit-Verzeichnisse auf dem Suchpfad stehen. Beides ist nicht verhandelbar. HotPDF.inc schaltet den Compiler mit {$MODE DELPHI} und {$H+} in seinem {$IFDEF FPC}-Block um und weist Älteres mit einem {$FATAL} zurück, sobald FPC_FULLVERSION unter 30202 liegt, sodass eine 3.0.x-Installation laut scheitert, statt eine kaputte Unit zu erzeugen. Das Lazarus-Runtime-Paket HotPDFLaz.lpk kodiert den Rest: LCL als Pflichtpaket und -Mdelphi als benutzerdefinierte Option
Die LCL-Anforderung überrascht Leute, die nur Konsolenausgabe wollen, aber sie ist strukturell. HPDFFPCCompat liefert die Delphi-VCL-Typen, für die Free Pascal kein Äquivalent hat, und mappt TMetafile und TMetafileCanvas auf LCL-Bitmap- und Canvas-Klassen, während es TRichEdit auf TMemo aliasiert; HPDFDoc aliasiert TPNGObject auf Graphics.TPortableNetworkGraphic. Behandeln Sie das als Compilezeit-Shims, nicht als Feature-Parität: Eine Bitmap-gestützte Metafile-Klasse hält die Unit im Kompilat, sie lässt die Metafile-Pfade nicht so laufen wie unter Delphi. Sogar der Nicht-GUI-Smoke-Test zieht Interfaces herein, und das Build-Skript übergibt -Fu für lcl\units\x86_64-win64 und das lazutils-Ausgabeverzeichnis
Warum D2009+ nicht zugleich das Versions-Gate sein kann
Es ist verlockend, den Free-Pascal-Build als modernen Compiler zu behandeln und einfach das neueste Delphi-Feature-Symbol zu definieren. HotPDF tut das nicht, und der Grund lohnt Klartext: D2009+ bedeutet nicht nur Unicode-Strings, es gate auch Units, deren öffentliche API mit anonymen Methoden ausgedrückt ist. Free Pascal 3.2.2 unterstützt weder Delphi-anonyme Methoden noch diese APIs, also würde das Ausleihen des Symbols Code hereinziehen, der nicht kompilieren kann. Die uses-Klausel von HPDFDoc trägt deshalb zwei getrennte bedingte Schwänze, und die Überlappung zwischen ihnen ist absichtlich, nicht zufällig
uses
// ...
HPDFJavaScript,
HPDFFormCalcGraph
{$IFDEF FPC}
, HPDFFPCCodecStubs,
HPDFCMS,
HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
, HPDFXFARuntime,
HPDFCMS,
HPDFWinCertSigner,
HPDFSignVerify,
HPDFSignatureBatch
{$ENDIF};
Warum bleiben die nativen Codecs beim Linker hängen?
Weil sie Win64-COFF-Objekte sind, die von einer bestimmten Toolchain erzeugt wurden, und kein Free-Pascal-Linker unter Win64 sie annimmt: weder der interne Linker noch der externe GNU-ld-Pfad. Das ist ein Objektdatei-ABI-Problem, kein Pascal-Problem, und keine Menge bedingter Quelltexte behebt es. Die Bibliothek nimmt den einzigen ehrlichen Weg, der offen ist. Jede {$L}-Direktive, die ein statisches Codec-Objekt hereinzieht, ist in {$IFNDEF FPC} gewickelt, sodass der Free-Pascal-Build sie schlicht weglässt, und HPDFFPCCodecStubs liefert dann jedes fehlende externe Symbol als Stub, der wirft, statt zurückzukehren
// 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;
Diese Stub-Tabelle ist lang, und ihr Lesen verrät genau, welche Fähigkeiten heute Delphi-only sind: die zlib-ng- und zopfli-deflate-Einsprungpunkte, libjpeg-Kompression und -Dekompression, den OpenJPEG-JPEG-2000-Codec, libtiff samt seiner Initialisierer je Kompression, JBIG2-Kodierung und -Dekodierung, die Little-CMS-Farbtransformations-Einsprungpunkte und die AES-Primitiven. Die Entwurfsentscheidung hinter den Stubs wiegt schwerer als die Liste. Ein fehlendes Symbol zur Linkzeit gibt Ihnen eine Wand undefinierter Referenzen aus einer Unit, die Sie nie angerührt haben; ein Stub, der ENotSupportedException wirft, gibt Ihnen einen Build, der läuft, eine Meldung mit dem Grund und einen Stacktrace, der auf die Aufrufstelle zeigt. Er bedeutet auch, dass ein Free-Pascal-Build nie stillschweigend falsche Bytes erzeugt, wo ein Delphi-Build korrekte liefern würde. Beachten Sie auch den Effekt zweiter Ordnung: Nicht vertrauenswürdige Image-Codecs in einem isolierten Prozess zu betreiben ist eine Entscheidung, die nur im Delphi-Build entsteht, denn ein Free-Pascal-Build hat überhaupt keinen nativen In-Process-Dekoder, den man sandboxen könnte
Kompression: die erste zu ändernde Zeile ist cmNone
Bevor Sie irgendetwas anderes portieren, setzen Sie Compression auf cmNone. THPDFCompressionMethod bietet genau zwei Werte, cmNone und cmFlateDecode, und der zweite führt geradewegs in die deflate-Einsprungpunkte, die im Free-Pascal-Build Stubs sind. Verifizieren Sie zuerst das Kernobjektmodell mit ausgeschalteter Kompression, dann entscheiden Sie, was Sie sonst noch brauchen. Das ist die Reihenfolge des mitgelieferten Smoke-Tests: ein einseitiges unkomprimiertes Dokument erzeugen, neu laden und bestätigen, dass die Seitenanzahl als eins zurückkam. Unkomprimierte Ausgabe ist größer, und sie ist weiterhin ein völlig gültiges 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 landet auf einem gestubbten 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.
Was geschieht mit dem parallelen Seiten-Rendering?
Es kompiliert weiter, liefert weiter korrekte Bitmaps und hört auf, parallel zu sein. THotPDF.RenderLoadedPagesParallel und THotPDF.RenderLoadedPagesParallelOrdered bauen auf TThread.CreateAnonymousThread mit einem Inline-procedure-Closure auf, den Free Pascal 3.2.2 nicht ausdrücken kann; der Free-Pascal-Zweig fährt daher einen deterministischen seriellen Fallback: Er geht die Seitenindizes der Reihe nach durch, ruft für jeden RenderLoadedPageToBitmap auf und zählt die Erfolge. Die API-Form, der Rückgabewert und das Ausgabearray bleiben unverändert, wodurch eine einzige Codebasis in beide Richtungen bauen kann
var
Bitmaps: THPDFBitmapArray;
Info: THPDFParallelRenderPipelineInfo;
Rendered: Integer;
begin
Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
Bitmaps, Info);
// Delphi: Info.WorkerCount ist, was das Speicherbudget erlaubte
// Free Pascal: Info.WorkerCount ist immer 1, Seiten in Indexreihenfolge
if Info.WorkerCount = 1 then
LogSerialFallback(Rendered, Info.RequestedWorkerCount);
Der Fallback ist nicht stumm, und das ist der Teil, den man beim eigenen Entwurf einkalkulieren sollte. Er füllt THPDFParallelRenderPipelineInfo ehrlich: PageCount aus der Anfrage, RequestedWorkerCount als Echo dessen, was Sie verlangten, WorkerCount auf 1 gesetzt sowie abgeschlossene und ausgelieferte Zählungen passend zu dem, was wirklich zurückkam. Code, der Info bereits inspiziert, um einen Fortschrittsbalken oder ein Speicherbudget zu bemessen, funktioniert weiter und liest die Wahrheit statt einer Annahme. Hängt Ihr Durchsatzplan an der parallelen Render-Pipeline und ihrem Backpressure-Modell, ist dieser Plan ein Delphi-Plan; auf Free Pascal kalkulieren Sie die single-threaded Kosten des Renderns einer Seite in eine Bitmap mal der Seitenanzahl
Welchen Build liefern Sie tatsächlich aus?
Wählen Sie nach Fähigkeit, nicht nach Vorliebe. Wenn Ihr Workflow Dokumentmontage, Text- und Vektorzeichnung, Formularbefüllung, Laden und Speichern ist, deckt ihn der Free-Pascal-Build unter Win64 ab, und Sie sollten mit ausgeschalteter Kompression validieren, bevor Sie irgendetwas einschalten. Geht es um JPEG-, JPEG-2000-, TIFF- oder JBIG2-Bilder, ICC-Farbtransformationen, komprimierte Ausgabe oder Durchsatz, der von vielen Kernen abhängt, bleiben Sie vorerst bei Delphi oder C++Builder. Die Grenze ziehen ein Objektdatei-ABI und ein fehlendes Sprachfeature, beide im Quelltext sichtbar statt in einer Support-Matrix vergraben, und beide scheitern mit einer benannten Fehlermeldung statt mit einem falschen Ergebnis
Das Free-Pascal-und-Lazarus-Paket kommt in derselben Distribution wie die Delphi- und C++Builder-Units, also deckt eine Lizenz beide ab, und Sie können den Lazarus-Pfad an Ihren eigenen Dokumenten testen, bevor Sie sich festlegen; die Produktseite der HotPDF Delphi PDF Component führt die aktuelle Compiler-Support-Matrix und die vollständige API-Referenz