Der Job war unauffällig: ein nächtlicher Delphi-Dienst, der gescannte Hypothekenarchive annimmt, Seiten zählt und jede Datei in die richtige Verarbeitungswarteschlange leitet. Monatelang lief er ruhig, bis ein 1,4-GB-Archiv eintraf. LoadFromFile parst die Cross-Reference-Daten und materialisiert für jedes der mehreren hunderttausend indirekten Objekte der Datei ein Objekt. In einem 32-Bit-Dienst drückte dieser Baum die Working Set mitten im Parsevorgang über die 2-GB-Adressraumgrenze. Die Korrektur war kein größerer Server. Der Job stellte nur eine Frage: Wie viele Seiten? Und diese Antwort erforderte nie, das Dokument überhaupt zu laden.
HotPDFs Direct File API existiert genau für diese Arbeitsklasse: PDF-Operationen auf Dateiebene aus Delphi und C++Builder, die vom Datenträger lesen, was sie benötigen, statt das ganze Dokumentmodell zu materialisieren. Zu wissen, zu welcher API-Ebene eine Operation gehört, entscheidet darüber, ob ein Dienst flachen Speicherverbrauch hat oder beim ersten übergroßen Eingang umfällt.
Was vollständiges Laden bringt, und was es kostet
Ein Dokument mit LoadFromFile zu laden bringt zufälligen Zugriff auf alles: jede Seite, jedes Objekt, bereit für Umstrukturierung, Inhaltsänderungen oder Neu-Serialisierung über SaveLoadedDocument. Diese Leistung ist das richtige Werkzeug für Seitenmanipulation, denn InsertPagesFromDocument und MovePage brauchen den Baum. Die Kosten sind proportional zum Dokument, nicht zu Ihrer Operation: Die Parsezeit skaliert mit der Objektanzahl, und residenter Speicher läuft auf ein Mehrfaches der Dateigröße hinaus, sobald Objektstrukturen und decodierte Streams berücksichtigt sind.
Der Widerspruch zeigt sich, wenn Eingabegrößen unbegrenzt sind. Kunden-Uploads, Scanner-Ausgaben und jahrzehntealte Archive respektieren die Annahmen eines Testkorpus nicht. Eine Pipeline, die jede Eingabe lädt, hat Speicheranforderungen, die von der größten Datei bestimmt werden, die je jemand einreicht. Eine Pipeline, die handlebasierte Lesezugriffe für die Fragen verwendet, bei denen das möglich ist, hat ungefähr konstante Speicheranforderungen. Für einen langlebigen Dienst ist dieser Unterschied wichtiger als rohe Geschwindigkeit.
Der Wechsel auf 64 Bit hebt die Grenze an, ändert aber nicht die Ökonomie: Ein Worker, der eine Datei im Gigabytebereich parst, verbraucht weiterhin Sekunden CPU und ein Mehrfaches der Datei im RAM, um Fragen zu beantworten, die die Dateistruktur direkt beantworten könnte. Parallelität verschärft es. Vier gleichzeitige große Ladevorgänge konkurrieren um dasselbe Speicherbudget, sodass der Durchsatz genau dann einbricht, wenn die Warteschlange am vollsten ist.
Handlebasierte Inspektion
Die Read-only-Ebene öffnet die Datei als Handle, beantwortet strukturelle Fragen und schließt sie wieder, ohne Objektbaum, ohne Seitenrendering, ohne proportionalen Speicher.
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Drei Regeln halten diese Ebene zuverlässig. Prüfen Sie das Handle: Ein nicht positiver Rückgabewert bedeutet, dass das Öffnen fehlgeschlagen ist, und ein Aufruf von DAGetPageCount gegen ein totes Handle ist genau der Fehler, der erst bei der fehlerhaften Kundendatei sichtbar wird. Paaren Sie jedes erfolgreiche Öffnen mit DACloseFile in einem finally-Block, denn ein Dienst, der Handles verliert, degradiert langsam statt sichtbar zu scheitern. Und kennen Sie die Kosten des Passwortparameters: DAOpenFileReadOnly akzeptiert ein Passwort, fällt bei verschlüsselten Eingaben intern aber auf einen vollständigen Parse zurück, um die Seitenzahl zu beantworten. Die Flat-Memory-Eigenschaft gilt nur für unverschlüsselte Dateien, daher sollten geschützte Eingaben zuerst durch DecryptFile laufen.
Die Handle-Ebene eignet sich auch als günstiges Triage-Gate. Dateien kommen von Kunden falsch benannt an, durch fehlgeschlagene Uploads abgeschnitten oder aus anderen Formaten umbenannt. Eine DAOpenFileReadOnly-Probe weist sie in Millisekunden an der Eingangstür zurück, mit einem klaren Fehler an der richtigen Datei, statt sie tief in einem Queue-Worker scheitern zu lassen, wo die Diagnose einen Nachmittag kostet.
Whole-file-Operationen: kopieren, entschlüsseln, verschlüsseln
Die zweite Ebene transformiert vollständige Dateien, ohne ihre Interna offenzulegen, und bildet die Arbeitspferde von Intake-Pipelines.
// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Jeder Aufruf hat eine eigene Rolle. DACopyFile ist die validierte Kopie aus einem Quarantäneverzeichnis in verwalteten Speicher. Der Aufruf öffnet und indiziert die PDF-Struktur während des Kopierens, sodass eine abgeschnittene oder Nicht-PDF-Eingabe hier fehlschlägt und nicht drei Stufen später. DecryptFile erzeugt eine entschlüsselte Kopie über einen direkten AES-256-Rewrite-Pfad, der den Aufbau des Objektbaums vermeidet, soweit die Eingabe das zulässt. Es ist das Large-file-Gegenstück zum Load-and-resave-Entschlüsselungsfluss, den der AES-256-Verschlüsselungsartikel beschreibt. EncryptFile ist das Spiegelbild: Passwortschutz wird während einer Kopie auf Dateiebene angewendet, mit denselben Key-Type- und Berechtigungsparametern wie im In-memory-Pfad.
Änderungen anhängen statt neu schreiben
Das in ISO 32000-1 §7.5.6 definierte inkrementelle Update ist die dritte Ebene: Die ursprünglichen Bytes bleiben auf dem Datenträger unverändert, und geänderte oder neue Objekte werden danach mit einem Cross-Reference-Abschnitt angehängt, der auf den ursprünglichen zurückkettet. Für ein 900-MB-Archiv, das eine hinzugefügte Seite braucht, sind die Schreibkosten das Delta, nicht die Datei.
// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // original bytes + delta
Die Disziplin hier: BeginIncrementalUpdate muss auf die Originaldatei zeigen, weil die angehängten Cross-Reference-Daten auf Offsets in ihr zurückverweisen. Und das Modell ist konstruktiv append-only. Jede inkrementelle Speicherung macht die Datei größer, nie kleiner, sodass ein Dokument, das jede Nacht gestempelt wird, unbegrenzt wächst, bis eine periodische Neu-Serialisierung, also Laden des Dokuments und Zurückschreiben über SaveLoadedDocument, es kompaktiert. Die Append-only-Eigenschaft macht das inkrementelle Update außerdem zum einzigen sicheren Weg, digital signierte Dokumente zu ändern, eine Einschränkung, die der Artikel über digitale Signaturen und PAdES untersucht; die zugrunde liegende Cross-Reference-Mechanik behandelt der Artikel über Objektstreams und inkrementelle Updates.
Eine Eigenschaft von Append-only-Speicherungen wird in Reviews leicht übersehen: Die ursprünglichen Bytes bleiben in der Datei und sind für jeden lesbar, der hinsieht. Ein inkrementelles Update, das eine Seite „ersetzt“, löscht die alte nicht. Es übersteuert sie in der aktuellen Revision, während die vorherige Revision wiederherstellbar bleibt. Verwenden Sie inkrementelle Updates nie, um sensible Inhalte zu entfernen; eine vollständige Neu-Serialisierung, LoadFromFile gefolgt von SaveLoadedDocument, die nur den aktuellen Zustand trägt, ist der richtige Weg, um Historie abzulegen, die ein Empfänger nicht sehen darf.
Die passende Ebene zur Operation wählen
Die Auswahllogik lässt sich auf vier Zeilen verdichten, und es lohnt sich, sie als explizite Routingentscheidung am Anfang einer Pipeline zu codieren, statt jeden Job seinen eigenen Pfad wählen zu lassen:
- Zählen, inspizieren, klassifizieren — ein Handle öffnen:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile. - Ganze Dateien verschieben, entschlüsseln oder verschlüsseln — Aufrufe auf Dateiebene:
DACopyFile,DecryptFile,EncryptFile. - Seiten umstrukturieren oder Dokumente zusammenführen — vollständig laden:
LoadFromFile, dannInsertPagesFromDocumentoderMovePage, danachSaveLoadedDocument. - Ein kleines Delta zu einer großen oder signierten Datei hinzufügen —
BeginIncrementalUpdateund speichern.
Gemischte Pipelines profitieren von einem Größenschwellwert vor dem Full-load-Pfad: Leiten Sie alles oberhalb einiger hundert Megabyte durch die Direct-File-Ebenen und stellen Sie echte Umstrukturierungsarbeit einem 64-Bit-Worker mit Speicherbudget in die Warteschlange. Der Schwellwert macht aus Out-of-memory-Abstürzen eine explizite, beobachtbare Routingentscheidung.
Welche Ebene einen Job auch verarbeitet: Schreiben Sie die Ausgabe zuerst unter einen temporären Namen und benennen Sie sie erst nach erfolgreicher Validierung an den Zielort um. Eine halb geschriebene Datei unter dem endgültigen Namen ist für die nächste Pipeline-Stufe nicht von einer guten zu unterscheiden. Die Direct-File-Aufrufe machen diese Validierung günstig, weil die Bestätigung der Ausgabe selbst eine Ein-Zeilen-Handle-Probe ist.
FAQ: große PDFs in Delphi-Diensten
Wie erhalte ich die Seitenzahl eines PDFs, ohne die ganze Datei zu laden?
DAOpenFileReadOnly plus DAGetPageCount, wie im obigen Inspektionsbeispiel. Der Speicherverbrauch bleibt unabhängig von der Dateigröße flach.
Warum wächst mein PDF nach jeder Speicherung?
Inkrementelle Updates hängen konstruktiv an; nichts wird entfernt. Kompaktieren Sie periodisch mit einem vollständigen Load-and-resave, also LoadFromFile und danach SaveLoadedDocument, wenn die angesammelten Revisionen nicht gebraucht werden.
Öffnet die Direct File API verschlüsselte PDFs?
Sie akzeptiert ein Passwort, aber verschlüsselte Eingaben laufen intern über einen vollständigen Parse und verlieren damit den Flat-Memory-Vorteil. Für geschützte Eingaben erzeugt DecryptFile mit dem Passwort eine unverschlüsselte Kopie, die der Rest der Pipeline auf Dateiebene verarbeiten kann.
Die Direct File API wird als Teil von HotPDF Component für Delphi und C++Builder ausgeliefert; die Produktseite verlinkt die vollständige Funktionsreferenz einschließlich der hier gezeigten Aufrufe für inkrementelle Updates.