In der PDFium Component for Delphi and Lazarus wirft die Zuweisung TPdf.Active := True nie, wenn ein PDF nicht lädt: TPdf.SetActive fängt jede Exception und lässt die Komponente inaktiv. Um den echten Fehler zu sehen, rufen Sie stattdessen TPdf.LoadDocument(Options, Report) auf. Dieses Overload hebt die ursprüngliche Exception erneut aus und füllt einen TPdfLoadReport mit dem Ladestatus, dem nativen PDFium-Fehlercode und der Auskunft, ob die Cross-Reference-Tabelle neu aufgebaut werden musste
Das Problem zeigt sich meist in Batch-Code. Ein Tabellenextraktions-Job läuft über einen Ordner mit 13 realweltlichen PDFs, die sich einen gemeinsamen TPdf teilen, und 7 davon kehren als Fehlschläge zurück. Keine der 7 Dateien ist tatsächlich kaputt. Die except-Blöcke um das Laden feuern nie, das Log macht die falschen Dateinamen verantwortlich, und der erste sichtbare Fehler ist ein nacktes EPdfError über eine inaktive Komponente, geworfen aus einem Property-Read mehrere Zeilen nach dem Laden, das wirklich scheiterte. Zwei getrennte Verhalten stapeln sich zu diesem Bild, und beide funktionieren wie entworfen
Warum wirft TPdf.Active := True nicht, wenn ein PDF nicht lädt?
TPdf.SetActive wickelt LoadDocument in ein try..except, das jede Exception-Klasse schluckt und die Komponente schlicht inaktiv lässt. Das Schlucken ist absichtlich: Derselbe Setter läuft, wenn ein Form-Designer im IDE Active umlegt, und ein schlechter Pfad darf die IDE nicht abschießen. Zur Laufzeit meldet TPdf.Active nur, ob ein natives Dokument-Handle existiert, liest also nach gescheitertem Laden False, und sonst passiert nichts. Was auch immer geworfen wurde, ist weg – ein EPdfError aus dem Parser, ein Stream-Fehler oder eine EAccessViolation aus einer halb gebundenen pdfium.dll. Die detaillierten DLL-Meldungen aus Diagnose von pdfium.dll-Ladefehlern in Delphi erreichen Ihren Handler nur über einen Aufruf, der sie nicht schluckt
Pdf.FileName := FileName;
try
Pdf.Active := True; // SetActive schluckt jede Lade-Exception
except
on E: Exception do
Log.Add(FileName + ': ' + E.Message); // läuft nie
end;
// Das Scheitern taucht stattdessen hier auf, als generisches EPdfError:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));
// Minimaler Fix für bestehenden Code: Active direkt nach der Zuweisung testen;
// seit v3.122.1 hält LastLoadReport den Text der geschluckten Fehler
Pdf.Active := True;
if not Pdf.Active then
Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);
Der Fehlschlag taucht schließlich am ersten bewachten Aufruf auf. TPdf.PageCount beginnt wie die meisten Dokument-Properties mit CheckActive, das ein EPdfError mit dem Komponentennamen wirft, aber weder Datei noch Ursache nennt. Pdf.Active direkt nach der Zuweisung zu testen macht aus einer falsch zugeschriebenen Absturzmeldung einen ehrlichen „failed“-Eintrag. Vor PDFiumPas v3.122.1 war der Grund an der Stelle verloren; seit v3.122.1 ersetzt die gescheiterte Zuweisung LastLoadReport durch einen plsFailed-Report mit dem Fehlertext, also überlebt die Ursache. Das Exception-Objekt selbst und das Byte-Level-Audit verlangen weiterhin einen anderen Einstiegspunkt
Warum scheitert ein wiederverwendeter TPdf ab der zweiten Datei?
TPdf.FileName lässt sich nur zuweisen, solange die Komponente inaktiv ist, also weist eine geteilte Instanz die zweite Datei zurück, bevor sie überhaupt versucht zu laden. TPdf.SetFileName beginnt mit CheckInactive, und derselbe Guard bewacht Password und FormFill. Nach dem ersten erfolgreichen Laden bleibt die Instanz aktiv, die nächste Zuweisung wirft, und wenn die Batch-Schleife diese Exception fängt und weiterzieht, landet der Fehler unter dem neuen Dateinamen, während das alte Dokument noch offen ist. Vermischt mit den geschluckten Ladefehlern hört das Log auf, zur Realität zu passen. In der 13-Datei-Reproduktion meldete eine geteilte Instanz 7 Fehlschläge, während ein frisches TPdf.Create(nil) pro Dokument alle 13 öffnete. Zwischen den Dateien Active := False zu setzen funktioniert auch, aber eine Instanz pro Dokument hält jede Datei von Konstruktion wegen isoliert
Was bringt TPdf.LoadDocument mit einem TPdfLoadReport?
TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) wirft die echte Exception und sagt Ihnen zusätzlich strukturiert, was passiert ist. Das Datei-Overload lädt FileName; Geschwister-Overloads nehmen TBytes oder Pointer und Größe, und LoadCustomDocument(AStream, AOwnsStream, Options, Report) deckt Streams ab. Jedes validiert die Optionen, prüft, dass die Instanz inaktiv ist, führt ein Byte-Level-Audit von Header, startxref, den xref-Abschnitten und dem %%EOF-Marker durch und macht dann das native Laden. Das Audit ist durch dieselbe Art von Grenzen begrenzt, die in Parser-Ressourcenbudgets für nicht vertrauenswürdige PDFs diskutiert werden: TPdfLoadOptions.Default setzt AuditByteLimit auf 256 MiB, MaxIssues auf 256, MaxXrefSections auf 1024 und MaxXrefEntries auf 4.000.000. Beim Scheitern setzt die Methode Report.Status := plsFailed und wirft erneut; weil Report an Ort und Stelle geschrieben wird, überleben seine Inhalte die Exception, und eine Kopie landet in TPdf.LastLoadReport
Die Report-Felder beantworten die Fragen, die ein Batch-Log tatsächlich braucht. Status ist einer von plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected oder plsFailed. NativeErrorCode hält FPDF_GetLastError, also trennt FPDF_ERR_PASSWORD (4) ein fehlendes oder falsches Passwort von einer als FPDF_ERR_FORMAT (3) gemeldeten beschädigten Datei. UsedRecovery, CrossReferenceTableValid und RecoveryRoute sagen, ob PDFium die xref-Tabelle neu aufbauen musste, und Issues listet jeden Audit-Befund mit Code, Severity, Offset, ObjectNumber und MessageText, mit gesetztem IssuesTruncated, wenn MaxIssues die Liste gestutzt hat
uses
SysUtils, Classes, TypInfo, FPdfView, PDFium;
procedure ProcessBatch(Files, Log: TStrings);
var
I: Integer;
Pdf: TPdf;
Options: TPdfLoadOptions;
Report: TPdfLoadReport;
begin
Options := TPdfLoadOptions.Default(plmCompatible);
for I := 0 to Files.Count - 1 do
begin
Pdf := TPdf.Create(nil); // eine Instanz pro Dokument
try
Pdf.FileName := Files[I];
try
Pdf.LoadDocument(Options, Report);
except
on E: Exception do
begin
// Report ist gefüllt, obwohl LoadDocument geworfen hat
if Report.NativeErrorCode = FPDF_ERR_PASSWORD then
Log.Add(Files[I] + ': password required')
else
Log.Add(Format('%s: %s (%s)', [Files[I],
GetEnumName(TypeInfo(TPdfLoadStatus), Ord(Report.Status)),
E.Message]));
Continue;
end;
end;
if Report.UsedRecovery then
Log.Add(Files[I] + ': opened after PDFium rebuilt the xref table');
ExtractTables(Pdf, Log);
finally
Pdf.Free;
end;
end;
end;
Wann sollten Sie mit plmStrict laden?
Nehmen Sie plmStrict, wann immer eine still reparierte Datei schlimmer ist als eine zurückgewiesene, etwa bei Archivaufnahme, Evidence-Handling oder einer Signatur-Pipeline. PDFium rekonstruiert stillschweigend eine kaputte Cross-Reference-Tabelle (ISO 32000-1 §7.5.4), indem es die Datei nach Objekten absucht – großartig für einen Viewer und ein Problem für alles, was exakt die Bytes verarbeiten muss, die es bekam. Nach dem nativen Laden fragt die Komponente FPDF_DocumentHasValidCrossReferenceTable ab. Im plmCompatible-Modus ergibt ein Rebuild plsLoadedWithRecovery plus eine plicNativeCrossReferenceRebuild-Warnung. Im plmStrict-Modus entlädt die Komponente das Dokument, setzt plsRejected, fügt plicStrictModeRejected hinzu und wirft ein EPdfError mit „Strict PDF load rejected the document“. Der Strict-Modus weist außerdem jeden Audit-Fehler zurück, und TPdfLoadOptions.Default(plmStrict) schaltet RequireFinalEndOfFileMarker ein, was ein fehlendes %%EOF oder Daten nach dem letzten (§7.5.5) von Warnung zu Fehler hochstuft. Das xref-Audit ergänzt die Objekt-Level-Checks aus Validieren von Objekt- und xref-Streams mit PDFium VCL
function AcceptForArchive(const FileName: string; out Reason: string): Boolean;
var
Pdf: TPdf;
Report: TPdfLoadReport;
I: Integer;
begin
Result := False;
Reason := '';
Pdf := TPdf.Create(nil);
try
Pdf.FileName := FileName;
try
Pdf.LoadDocument(TPdfLoadOptions.Default(plmStrict), Report);
Result := True; // gültiges xref, keine Audit-Fehler
except
on E: EPdfError do
begin
Reason := E.Message;
for I := 0 to High(Report.Issues) do
if Report.Issues[I].Severity = plisError then
Reason := Reason + sLineBreak + Format(' at offset %d: %s',
[Report.Issues[I].Offset, Report.Issues[I].MessageText]);
end;
end;
finally
Pdf.Free;
end;
end;
Wo hört TPdf.LastLoadReport auf, die Wahrheit zu sagen?
TPdf.LastLoadReport ist erst nach einem LoadDocument-Overload mit Optionen vollständig, denn nur diese Overloads fahren das Byte-Audit. Ein erfolgreiches Active := True schreibt einen Compatible-Mode-Report ohne Byte-Audit, also bleibt AuditAttempted False. Vor PDFiumPas v3.122.1 schrieb ein gescheitertes gar nichts, was bedeutete, dass auf einer geteilten Instanz LastLoadReport weiterhin die vorige Datei beschrieb, oft mit einem beruhigenden plsLoaded. Seit v3.122.1 ersetzt jede gescheiterte Ladung den Report: Ein gescheitertes Active := True, das die Komponente weiterhin inaktiv lässt, ohne zu werfen, und ein gescheiterter schlichter LoadDocument- oder LoadCustomDocument-Aufruf zeichnen plsFailed mit dem Fehlertext auf, ebenfalls ohne Audit. Zwei weitere Lücken zählen in der Praxis. Optionsvalidierung und CheckInactive laufen, bevor der Report initialisiert ist, also wirft ein negatives AuditByteLimit oder eine bereits aktive Instanz, ohne einen Report zu erzeugen. Und NativeErrorCode ist nur aussagekräftig, wenn PDFium den Parse tatsächlich versucht hat; bei einer fehlenden Datei wirft der Wrapper, bevor PDFium läuft, also loggen Sie stattdessen ErrorMessage und den Exception-Text
Die praktische Regel ist kurz. Behalten Sie Active := True für designergebundene Viewer, wo eine inaktive Komponente ein akzeptables Ergebnis ist. Überall sonst, und vor allem in Batch- und Server-Code, erzeugen Sie ein TPdf pro Dokument, rufen LoadDocument(Options, Report) auf, fangen die geworfene Exception und loggen Report.Status, NativeErrorCode und die Fehler-Level-Issues zusammen mit dem Dateinamen. Der Preis sind ein paar Zeilen pro Aufrufstelle, und jeder Fehlschlag wird der richtigen Datei mit ihrer echten Ursache zugeordnet
Die Load-Report-API, der Strict-Modus und das Byte-Level-Audit kommen mit der PDFium Component for Delphi, C++Builder and Lazarus, neben Rendering, Textextraktion, Formularbefüllung und PDF/A-Validierung