Τεχνικό Άρθρο

HotPDF σε Free Pascal και Lazarus: όρια υποστήριξης Win64

Η σύντομη απάντηση σε αυτό το ticket υποστήριξης είναι ναι, με όρια. Το HotPDF 2.730.0 χτίζει σε Free Pascal 3.2.2 και Lazarus 4.6 για Win64, και οι βασικές διαδρομές create, load και save δουλεύουν. Αυτό που δεν ακολουθεί είναι οτιδήποτε στηρίζεται σε statically linked native codec object ή σε ανώνυμες μεθόδους της Delphi

Η ερώτηση συνήθως φτάνει με τον ίδιο τρόπο: μια ομάδα τυποποιεί σε Lazarus για ένα cross-platform εργαλείο, ή κληρονομεί μια codebase Free Pascal, και θέλει το ίδιο PDF component που ήδη έχει license για Delphi. Το porting μιας ώριμης βιβλιοθήκης Delphi σπάνια είναι θέμα σύνταξης. Το ενδιαφέρον μέρος είναι τι αποκαλύπτει το port για το πού ήταν ήσυχα συνδεδεμένη η βιβλιοθήκη με ένα toolchain, και εδώ η σύνδεση κάθεται σε δύο πολύ συγκεκριμένα σημεία: το object-file ABI των bundled codecs, και τις δυνατότητες μεταγλωττιστή που κρύβονται πίσω από ένα σύμβολο έκδοσης

Μήτρα δυνατοτήτων που συγκρίνει το Delphi build του HotPDF με το Free Pascal 3.2.2 και Lazarus 4.6 Win64 build, δείχνοντας ποιες διαδρομές εγγράφων είναι κοινές και ποια API codecs, συμπίεσης, παράλληλης απόδοσης και ανώνυμων μεθόδων καταλήγουν σε stub που πετάει exception
Οι βασικές διαδρομές create, load και save είναι πανομοιότυπες και στα δύο builds, και το κενό κάθεται εξ ολοκλήρου στα statically linked codecs και στα API ανώνυμων μεθόδων

Τι χρειάζεται το Free Pascal 3.2.2 πριν συνταχθεί το HPDFDoc

Το HotPDF συντάσσεται κάτω από Free Pascal μόνο σε Delphi mode, και μόνο όταν οι κατάλογοι units LCL του Lazarus είναι στη διαδρομή αναζήτησης. Κανένα από τα δύο δεν είναι διαπραγματεύσιμο. Το HotPDF.inc αλλάζει τον μεταγλωττιστή με {$MODE DELPHI} και {$H+} μέσα στο block {$IFDEF FPC} του και αρνείται οτιδήποτε παλαιότερο με {$FATAL} όταν το FPC_FULLVERSION είναι κάτω από 30202, ώστε μια εγκατάσταση 3.0.x να αποτυγχάνει δυνατά αντί να παράγει χαλασμένο unit. Το runtime package Lazarus HotPDFLaz.lpk κωδικοποιεί τα υπόλοιπα: LCL ως required package και -Mdelphi ως custom option

Η απαίτηση LCL εκπλήσσει όσους θέλουν μόνο έξοδο κονσόλας, αλλά είναι δομική. Το HPDFFPCCompat προμηθεύει τους τύπους VCL της Delphi για τους οποίους το Free Pascal δεν έχει αντίστοιχο, αντιστοιχίζοντας τα TMetafile και TMetafileCanvas σε κλάσεις bitmap και canvas του LCL και κάνοντας alias το TRichEdit στο TMemo, ενώ το HPDFDoc κάνει alias το TPNGObject στο Graphics.TPortableNetworkGraphic. Αντιμετωπίστε τα ως compile-time shims, όχι ως ισοδυναμία δυνατοτήτων: μια κλάση metafile στηριγμένη σε bitmap κρατά το unit να συντάσσεται, δεν κάνει τις διαδρομές metafile να συμπεριφέρονται όπως στη Delphi. Ακόμα και το non-GUI smoke test τραβάει το Interfaces, και το build script περνά -Fu για lcl\units\x86_64-win64 και τον κατάλογο εξόδου lazutils

Γιατί το D2009+ δεν μπορεί να λειτουργήσει και ως πύλη έκδοσης

Είναι δελεαστικό να αντιμετωπίσετε το Free Pascal build ως σύγχρονο μεταγλωττιστή και απλώς να ορίσετε το νεότερο σύμβολο δυνατοτήτων της Delphi. Το HotPDF δεν το κάνει, και η αιτία αξίζει να ειπωθεί ευθέως: το D2009+ δεν σημαίνει μόνο Unicode strings, φράσσει και units της οποίας το δημόσιο API εκφράζεται με ανώνυμες μεθόδους. Το Free Pascal 3.2.2 δεν υποστηρίζει ούτε ανώνυμες μεθόδους Delphi ούτε εκείνα τα API, οπότε ο δανεισμός του συμβόλου θα έσερνε κώδικα που δεν μπορεί να συνταχθεί. Η uses ρήτρα του HPDFDoc φέρει επομένως δύο ξεχωριστά conditional tails, και η επικάλυψη μεταξύ τους είναι σκόπιμη και όχι τυχαία

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

Γιατί σταματούν τα native codecs στον linker;

Επειδή είναι Win64 COFF objects που εκπέμπει ένα συγκεκριμένο toolchain, και κανένας linker του Free Pascal σε Win64 δεν θα τα καταναλώσει: ούτε ο εσωτερικός linker, ούτε η εξωτερική διαδρομή GNU ld. Αυτό είναι πρόβλημα object-file ABI, όχι πρόβλημα Pascal, και καμιά ποσότητα conditional source δεν το διορθώνει. Η βιβλιοθήκη παίρνει τη μόνη ειλικρινή διαδρομή διαθέσιμη. Κάθε directive {$L} που τραβάει ένα static codec object είναι τυλιγμένο σε {$IFNDEF FPC}, ώστε το Free Pascal build απλώς τα παραλείπει, και το HPDFFPCCodecStubs μετά προμηθεύει κάθε λείπον εξωτερικό σύμβολο ως stub που πετάει exception αντί να επιστρέφει

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

Ο πίνακας stubs είναι μεγάλος, και η ανάγνωσή του σας λέει ακριβώς ποιες δυνατότητες είναι Delphi-only σήμερα: τα entry points deflate του zlib-ng και zopfli, η συμπίεση και αποσυμπίεση libjpeg, το codec OpenJPEG JPEG 2000, το libtiff και οι αρχικοποιήσεις ανά συμπίεση, encode και decode JBIG2, τα entry points μετασχηματισμού χρωμάτων Little-CMS, και τα primitives AES. Η επιλογή σχεδιασμού πίσω από τα stubs μετράει περισσότερο από τη λίστα. Ένα λείπον σύμβολο στον linker σάς δίνει έναν τοίχο undefined references από unit που δεν αγγίξατε ποτέ· ένα stub που πετάει ENotSupportedException σάς δίνει ένα build που τρέχει, ένα μήνυμα που ονομάζει την αιτία, και stack trace που δείχνει το call site. Σημαίνει επίσης ότι ένα Free Pascal build δεν παράγει ποτέ σιωπηλά λάθος bytes εκεί όπου ένα Delphi build θα παρήγαγε σωστά. Σημειώστε και το δευτερογενές αποτέλεσμα: το running untrusted image codecs in an isolated process είναι απόφαση που προκύπτει μόνο στο Delphi build, επειδή ένα Free Pascal build δεν έχει καθόλου in-process native decoder για sandbox

Στη Delphi τα static codec objects του HotPDF κάνουν link και τρέχουν natively, ενώ το Free Pascal Win64 build παραλείπει τα link directives και δρομολογεί κάθε λείπον εξωτερικό σύμβολο σε stub που πετάει επώνυμο exception στο call site
Το να παραλείπετε τα link directives και να κάνετε stub κάθε εξωτερικού συμβόλου μετατρέπει έναν τοίχο undefined references σε build που τρέχει και ονομάζει τα δικά του όρια

Συμπίεση: η πρώτη γραμμή που αλλάζει είναι cmNone

Πριν κάνετε port οτιδήποτε άλλο, ορίστε το Compression σε cmNone. Το THPDFCompressionMethod προσφέρει ακριβώς δύο τιμές, cmNone και cmFlateDecode, και η δεύτερη δρομολογεί ευθεία στα entry points deflate που είναι stubs σε Free Pascal build. Επαληθεύστε πρώτα το βασικό object model με τη συμπίεση κλειστή, μετά αποφασίστε τι άλλο χρειάζεστε. Αυτή είναι η σειρά που χρησιμοποιεί το shipped smoke test: δημιουργία μονοσέλιδου ασυμπίεστου εγγράφου, επαναφόρτωση, και βεβαίωση ότι το πλήθος σελίδων γύρισε ένα. Η ασυμπίεστη έξοδος είναι μεγαλύτερη, και παραμένει ένα απολύτως έγκυρο 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 φτάνει σε stubbed σύμβολο
    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.

Τι γίνεται με την παράλληλη απόδοση σελίδων;

Εξακολουθεί να συντάσσεται, εξακολουθεί να επιστρέφει σωστά bitmaps, και σταματά να είναι παράλληλη. Το THotPDF.RenderLoadedPagesParallel και το THotPDF.RenderLoadedPagesParallelOrdered χτίζονται πάνω σε TThread.CreateAnonymousThread με inline procedure closure, που το Free Pascal 3.2.2 δεν μπορεί να εκφράσει, ώστε ο κλάδος Free Pascal τρέχει έναν ντετερμινιστικό σειριακό fallback: περπατά τα page indices με τη σειρά, καλεί RenderLoadedPageToBitmap για καθένα, και μετρά τις επιτυχίες. Το σχήμα API, η τιμή επιστροφής και το output array δεν αλλάζουν, και αυτό επιτρέπει σε μια codebase να χτίζεται και με τους δύο τρόπους

Η ίδια κλήση παράλληλης απόδοσης HotPDF τρέχει σε αλληλεπικαλυπτόμενα worker threads στη Delphi και περπατά τα page indices σειριακά στο Free Pascal, με την εγγραφή pipeline info να αναφέρει worker count ένα αντί να κρύβει το fallback
Ο κλάδος Free Pascal κρατά το σχήμα API και το output array αναφέροντας worker count ένα, ώστε κώδικας που ήδη διαβάζει την εγγραφή info να βλέπει την αλήθεια
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: το Info.WorkerCount είναι ό,τι επέτρεπε το budget μνήμης
  // Free Pascal: το Info.WorkerCount είναι πάντα 1, σελίδες σε σειρά index
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

Το fallback δεν είναι σιωπηλό, και αυτό είναι το μέρος που αξίζει σχεδιασμό γύρω του. Γεμίζει το THPDFParallelRenderPipelineInfo ειλικρινά: PageCount από το αίτημα, RequestedWorkerCount που αντανακλά ό,τι ζητήσατε, WorkerCount ορισμένο σε 1, και τα πλήθη completed και delivered ταιριασμένα με ό,τι όντως γύρισε. Κώδικας που ήδη εξετάζει το Info για να διαστάσει μια μπάρα προόδου ή ένα budget μνήμης συνεχίζει να δουλεύει και διαβάζει την αλήθεια και όχι μια υπόθεση. Αν το σχέδιο throughput σας εξαρτάται από το pipeline παράλληλης απόδοσης και το μοντέλο backpressure του, αυτό το σχέδιο είναι σχέδιο Delphi· σε Free Pascal, προβλέψτε το single-threaded κόστος του rendering σελίδας σε bitmap επί το πλήθος σελίδων

Ποιο build πρέπει πραγματικά να στείλετε;

Διαλέξτε κατά δυνατότητα, όχι κατά προτίμηση. Αν το workflow σας είναι συναρμολόγηση εγγράφων, κείμενο και διανυσματικό σχέδιο, συμπλήρωση φορμών, φόρτωση και αποθήκευση, το Free Pascal build σε Win64 το καλύπτει, και πρέπει να επαληθεύσετε με συμπίεση κλειστή πριν ενεργοποιήσετε οτιδήποτε. Αν περιλαμβάνει εικόνες JPEG ή JPEG 2000 ή TIFF ή JBIG2, μετασχηματισμούς χρωμάτων ICC, συμπιεσμένη έξοδο, ή throughput που εξαρτάται από πολλούς πυρήνες, μείνετε σε Delphi ή C++Builder προς το παρόν. Το όριο χαράσσεται από ένα object-file ABI και ένα λείπον γλωσσικό χαρακτηριστικό, και τα δύο ορατά στον πηγαίο κώδικα και όχι θαμμένα σε μήτρα υποστήριξης, και τα δύο αποτυγχάνουν με επώνυμο σφάλμα και όχι με λάθος αποτέλεσμα

Το πακέτο Free Pascal και Lazarus στέλνεται στην ίδια διανομή με τα units Delphi και C++Builder, ώστε μια άδεια καλύπτει και τα δύο και μπορείτε να δοκιμάσετε τη διαδρομή Lazarus στα δικά σας έγγραφα πριν την υιοθετήσετε· η σελίδα προϊόντος HotPDF Delphi PDF Component φέρει την τρέχουσα μήτρα υποστήριξης μεταγλωττιστών και την πλήρη αναφορά API