Teknik Makale

Delphi'de Sessiz PDF Yükleme Hataları: PDFium Load Report

Delphi ve Lazarus için PDFium Component'te, bir PDF yüklenemediğinde TPdf.Active := True atamak asla yükseltmez: TPdf.SetActive her istisnayı yakalar ve bileşeni etkin dışı bırakır. Gerçek hatayı görmek için bunun yerine TPdf.LoadDocument(Options, Report) çağırın. O aşırı yükleme özgün istisnayı yeniden yükseltir ve bir TPdfLoadReport'u yükleme durumu, yerli PDFium hata kodu ve çapraz referans tablosunun yeniden kurulmak zorunda kalıp kalmadığıyla doldurur

Sorun genellikle toplu kodda ortaya çıkar. Bir tablo çıkarma işi, tek bir paylaşımlı TPdf ile 13 gerçek dünya PDF'inden oluşan bir klasörü gezer ve 7'si başarısızlık olarak döner. O 7 dosyadan hiçbiri aslında bozuk değildir. Yükleme çevresindeki except blokları hiç tetiklenmez, günlük yanlış dosya adlarını suçlar ve ilk görünür hata, gerçekten başarısız olan yüklemenin birkaç satır sonra yapıldığı bir özellik okumasından yükselen, etkin dışı bileşen hakkında çıplak bir EPdfError'dur. İki ayrı davranış üst üste binerek o tabloyu üretir ve ikisi de tasarlandığı gibi çalışır

Bir PDF yüklenemediğinde TPdf.Active := True neden yükseltmez?

TPdf.SetActive, LoadDocument'i her istisna sınıfını yutan ve bileşeni basitçe etkin dışı bırakan bir try..except içine alır. Yutma bilinçlidir: aynı setter, bir form tasarımcısı IDE'de Active'i değiştirdiğinde de koşar ve kötü bir yol IDE'yi çökertmemelidir. Çalışma zamanında TPdf.Active, yalnızca bir yerli belge tanıtıcısının var olup olmadığını bildirir; başarısız bir yüklemenin ardından False okur ve başka hiçbir şey olmaz. Yükselen her ne ise gitmiştir; ayrıştırıcıdan bir EPdfError, bir akış hatası ya da yarım bağlanmış bir pdfium.dll'den bir EAccessViolation olmuş olsun. Delphi'de pdfium.dll yükleme başarısızlıklarını teşhis makalesinde anlatılan ayrıntılı DLL iletileri, onları yutmayan bir çağrı üzerinden handler'ınıza ulaşır

PDFium Component'te iki yükleme yolu: Active true atamak setter'da her istisnayı yutar ve başarısızlığı, CheckActive'in etkin dışı bileşen hakkında bir EPdfError yükselttiği ilk korumalı çağrıya erteler; TPdfLoadOptions ve TPdfLoadReport ile LoadDocument ise başlığı, startxref'i, xref'i ve dosya sonu işaretini denetler, sonra özgün istisnayı gerçek nedenle birlikte yeniden yükseltir
Yutma bilinçlidir, çünkü IDE tasarımcısı setter'ı paylaşır; toplu kod, yükselten, raporlayan ve dosyanın gerçek hikayesini anlatan aşırı yüklemeye ihtiyaç duyar
Pdf.FileName := FileName;
try
  Pdf.Active := True;       // SetActive her yükleme istisnasını yutar
except
  on E: Exception do
    Log.Add(FileName + ': ' + E.Message);   // hiç koşmaz
end;
// Başarısızlık bunun yerine burada ortaya çıkar; jenerik bir EPdfError olarak:
// 'Cannot perform this operation on an inactive Pdf1 component'
Log.Add(Format('%s: %d pages', [FileName, Pdf.PageCount]));

// Mevcut kod için asgari düzeltme: atamanın hemen ardından Active'i sına;
// v3.122.1'den beri LastLoadReport, yutulan hatanın metnini korur
Pdf.Active := True;
if not Pdf.Active then
  Log.Add(FileName + ': load failed: ' + Pdf.LastLoadReport.ErrorMessage);

Başarısızlık nihayet ilk korumalı çağrıda ortaya çıkar. Çoğu belge özelliği gibi TPdf.PageCount, CheckActive ile başlar; bu, bileşeni adandıran ama dosyayı ve nedeni adandırmayan bir EPdfError yükseltir. Atamanın hemen ardından Pdf.Active'i sınamak, yanlış atfedilen bir çöküşü dürüst bir "başarısız" girdisine çevirir. PDFiumPas v3.122.1 öncesinde neden bu noktada kayboluyordu; v3.122.1'den beri başarısız atama, hata metnini taşıyan bir plsFailed raporuyla LastLoadReport'u değiştirir; böylece neden hayatta kalır. İstisna nesnesinin kendisi ile bayt düzeyi denetim hâlâ farklı bir giriş noktası gerektirir

Tek bir TPdf'i yeniden kullanmak ikinci dosyadan itibaren neden başarısız olur?

TPdf.FileName yalnızca bileşen etkin dışıyken atanabilir; paylaşımlı bir örnek, ikinci dosyayı yükmeyi daha denemeden reddeder. TPdf.SetFileName CheckInactive ile başlar ve aynı koruma Password ile FormFill'i de korur. İlk başarılı yüklemenin ardından örnek etkin kalır, sonraki atama yükseltir ve toplu döngü o istisnayı yakalayıp devam ederse hata, eski belge hâlâ açıkken yeni dosya adının altına düşer. Yutulan yükleme başarısızlıklarıyla karışınca günlük, gerçekle eşleşmeyi bırakır. 13 dosyalık yeniden üretimde paylaşımlı bir örnek 7 başarısızlık bildirirken, belge başına taze bir TPdf.Create(nil) 13'ünü de açtı. Dosyalar arasında Active := False ayarlamak da işe yarar; ama belge başına bir örnek, her dosyayı yapısından izole tutar

Paylaşımlı bir PDFium TPdf'in ikinci dosyadan itibaren başarısız olma zaman çizelgesi: ilk yüklemenin ardından örnek etkin kalır, sonraki FileName ataması her yükleme girişiminden önce CheckInactive içinde yükselir ve toplu döngü, eski belge hâlâ açıkken hatayı yeni dosya adı altına yazar — 13 dosyalık toplu işteki 7 yanlış başarısızlığın ardındaki tuzak
SetFileName, CheckInactive ile korunur; paylaşımlı bir örnek ikinci dosyayı denemeden reddeder; her belgeyi kendi TPdf'iyle izole edin, günlük yeniden gerçekle eşleşsin

TPdfLoadReport ile TPdf.LoadDocument size ne kazandırır?

TPdf.LoadDocument(const Options: TPdfLoadOptions; out Report: TPdfLoadReport) gerçek istisnayı yükseltir ve olup biteni yapılandırılmış biçimde de söyler. Dosya aşırı yüklemesi FileName'i yükler; kardeş aşırı yüklemeler TBytes ya da işaretçi ve boyut alır ve LoadCustomDocument(AStream, AOwnsStream, Options, Report) akışları kapsar. Her biri seçenekleri doğrular, örneğin etkin dışı olduğunu denetler, başlığın, startxref'in, xref bölümlerinin ve %%EOF işaretinin bayt düzeyi denetimini koşar, sonra yerli yüklemeyi yapar. Denetim, güvenilmeyen PDF'ler için ayrıştırıcı kaynak bütçeleri makalesinde tartışılan türden sınırlarla çevrilidir: TPdfLoadOptions.Default, AuditByteLimit'i 256 MiB'ye, MaxIssues'i 256'ya, MaxXrefSections'i 1024'e ve MaxXrefEntries'i 4.000.000'a kurar. Başarısızlıkta yöntem Report.Status := plsFailed kurar ve yeniden yükseltir; Report yerinde yazıldığı için içeriği istisnayı aşar ve bir kopyası TPdf.LastLoadReport'ta saklanır

Rapor alanları, bir toplu iş günlüğünün gerçekten gereksindiği soruları yanıtlar. Status, plsNotAttempted, plsLoaded, plsLoadedWithRecovery, plsRejected ya da plsFailed'den biridir. NativeErrorCode, FPDF_GetLastError'ı tutar; böylece FPDF_ERR_PASSWORD (4), FPDF_ERR_FORMAT (3) olarak bildirilen bozuk dosyadan eksik ya da yanlış parolayı ayırır. UsedRecovery, CrossReferenceTableValid ve RecoveryRoute, PDFium'un xref tablosunu yeniden kurmak zorunda kalıp kalmadığını söyler ve Issues, her denetim bulgusunu Code, Severity, Offset, ObjectNumber ve MessageText ile listeler; MaxIssues listeyi kısalttığında IssuesTruncated kurulur

PDFium Component'in LoadDocument hattı ve TPdfLoadReport'u: seçenek doğrulaması ile etkin dışı denetimi hiç rapor var olmadan yükseltir; bayt denetimi başlığı, startxref'i, xref bölümlerini ve dosya sonu işaretini gezer; yerli yükleme FPDF_GetLastError'ı kaydeder ve sonuçlar loaded, xref yeniden kurulumundan sonra loaded with recovery, katı red ya da failure'a dallanır
Status, NativeErrorCode ve sorun listesi, bir toplu iş günlüğünün gereksindiğini yanıtlar; bayt denetimini yalnızca seçenekli bir aşırı yükleme ekler; v3.122.1'den beri başarısız bir Active := True, LastLoadReport'a yine de plsFailed kaydeder
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);          // belge başına bir örnek
    try
      Pdf.FileName := Files[I];
      try
        Pdf.LoadDocument(Options, Report);
      except
        on E: Exception do
        begin
          // LoadDocument yükselse bile Report dolar
          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;

plmStrict ile ne zaman yüklemelisiniz?

Sessizce onarılmış bir dosyanın reddedilmiş olandan daha kötü olduğu her durumda plmStrict kullanın; arşiv kabulü, kanıt işleme ya da bir imzalama hattı gibi. PDFium, bozuk bir çapraz referans tablosunu (ISO 32000-1 §7.5.4) dosyayı nesneler için tarayarak sessizce yeniden kurar; bu, bir görüntüleyici için harikadır ve kendisine verilen baytları tam olarak işlemesi gereken her şey için bir sorundur. Yerli yüklemenin ardından bileşen FPDF_DocumentHasValidCrossReferenceTable'ı sorar. plmCompatible modunda bir yeniden kurulum, plsLoadedWithRecovery artı bir plicNativeCrossReferenceRebuild uyarısı verir. plmStrict modunda bileşen belgeyi boşaltır, plsRejected kurar, plicStrictModeRejected ekler ve "Strict PDF load rejected the document" iletisiyle EPdfError yükseltir. Katı mod ayrıca her denetim hatasını reddeder ve TPdfLoadOptions.Default(plmStrict), RequireFinalEndOfFileMarker'ı açar; bu, eksik bir %%EOF'u ya da sondakinin ardındaki veriyi (§7.5.5) uyarıdan hataya çeker. Xref denetimi, PDFium VCL ile object ve xref akışlarını doğrulama makalesindeki nesne düzeyi denetimleri tamamlar

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;               // geçerli xref, denetim hatası yok
    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;

TPdf.LastLoadReport gerçeği söylemeyi nerede bırakır?

TPdf.LastLoadReport, yalnızca seçenek alan bir LoadDocument aşırı yüklemesinden sonra tamdır; çünkü bayt denetimini koşan yalnızca o aşırı yüklemelerdir. Başarılı bir Active := True, bayt denetimsiz uyumlu mod bir rapor yazar; böylece AuditAttempted False kalır. PDFiumPas v3.122.1 öncesinde başarısız olan hiçbir şey yazmıyordu; bu, paylaşımlı bir örnek üzerinde LastLoadReport'un hâlâ önceki dosyayı betimlediği, çoğu zaman güven veren bir plsLoaded ile olduğu anlamına gelirdi. v3.122.1'den beri her başarısız yükleme raporu değiştirir: hâlâ yükseltmeden bileşeni etkin dışı bırakan başarısız bir Active := True ile başarısız bir düz LoadDocument ya da LoadCustomDocument çağrısı, hata metniyle plsFailed kaydeder; bunlar da denetimsizdir. Pratikte önemli iki boşluk daha vardır. Seçenek doğrulaması ile CheckInactive, rapor kurulmadan önce koşar; negatif bir AuditByteLimit ya da hâlihazırda etkin bir örnek, rapor üretmeksizin yükseltir. Bir de NativeErrorCode, yalnızca PDFium ayrıştırmayı fiilen denediğinde anlamlıdır; eksik dosya için sarmalayıcı, PDFium koşmadan önce yükseltir; o yüzden bunun yerine ErrorMessage'i ve istisna metnini günlüğe yazın

Pratik kural kısadır. Active := True'yu, etkin dışı bileşenin kabul edilebilir bir sonuç olduğu tasarımcıya bağlı görüntüleyicilerde tutun. Geri kalan her yerde ve her şeyden önce toplu iş ile sunucu kodunda, belge başına bir TPdf kurun, LoadDocument(Options, Report) çağırın, yükselttiği istisnayı yakalayın ve Report.Status, NativeErrorCode ile hata düzeyindeki Issues'i dosya adıyla birlikte günlüğe yazın. Maliyet, çağrı noktası başına birkaç satırdır ve her başarısızlık, gerçek nedeniyle doğru dosyaya atfedilir

Yükleme raporu API'si, katı mod ve bayt düzeyi denetim, Delphi, C++Builder ve Lazarus için PDFium Component ile birlikte gelir; yanında render, metin çıkarma, form doldurma ve PDF/A doğrulaması da vardır