Teknik Makale

İsteğe Bağlı PDFium Export'ları: Delphi'de Yetenek Kapıları

pdfium.dll'iniz sorunsuz yüklenir ve bir prosedür hâlâ eksiktir. PDFium Component bunu, bindinglerini iki sınıfa bölerek ele alır: yüklemeyi tamamen durduran CheckGetProcAddress üzerinden çözülen zorunlu export'lar ve bunun yerine bir nil işaretçisi ve arkasında bir yetenek kontrolü bırakan TryGetProcAddress üzerinden çözülen isteğe bağlı export'lar

Bu, bulunamayan bir DLL ile aynı problem değildir. Uygulamanız kötü bir EXE formatı hatası, eksik bir dosya veya bir mimari uyuşmazlığıyla ölüyorsa, o hikaye pdfium.dll dağıtımı ve yükleme hatalarını teşhis etme üzerine eşlik eden makalede anlatılmıştır. Burada yükleyici başarılı oldu. Modül tutamacı geçerli, yüzlerce export çözüldü ve çalışma yine de ilk sayfanız render edilmeden bitiyor çünkü daha yeni bir PDFium derlemesinde gelen bir giriş noktası, diskteki ikili dosyada yok

Eksik bir export tüm kütüphaneyi neden bozar?

Çünkü zorunlu bir binding sert bir sözleşmedir ve tek bir hep-ya-da-hiç bağlama sırasında zorlanır. PDFium Component, tüm export tablosunu LoadLibrary içinde, art arda CheckGetProcAddress çağrılarıyla çözer. İlk nil sonuç EPdfError fırlatır ve bunu yapmadan önce UnloadLibrary'yi çağırır, ki bu kasıtlıdır: aksi halde kısmi bir bağlama, zaten çözülmüş işaretçileri, aşağı akıştaki her Assigned korumasını sessizce yenerek, serbest bırakılmak üzere olan bir modüle işaret eder hâlde bırakırdı

Sonuç, insanları buraya getiren hata modudur. Bileşeni yükseltirsiniz, iki yıldır gönderdiğiniz aynı pdfium.dll'i gönderirsiniz ve uygulama başlamaz. Hata, hiç çağırmadığınız bir özellik için bir export adlandırır. Çağrı noktasında yaptığınız hiçbir şey yardımcı olmaz, çünkü çağrı noktası hiç çalışmaz; hata, hiçbir belge açılmadan önce, bağlama sırasında gerçekleşti

function CheckGetProcAddress(const Name: string): Pointer;
begin
  Result := GetProcAddress(PDFiumLibrary, PChar(Name));
  if Result = nil then
  begin
    // A missing required export means the deployed pdfium.dll is older
    // than this build of the binding. Drop every pointer resolved so far
    // so no caller can reach into the module we are about to free.
    UnloadLibrary;
    raise EPdfError.Create('Required PDFium export not found: ' + Name);
  end;
end;

function TryGetProcAddress(const Name: string): Pointer;
begin
  // Optional export. nil is a legitimate answer here; every caller is
  // required to test Assigned() before dereferencing the variable.
  Result := GetProcAddress(PDFiumLibrary, PChar(Name));
end;

Zorunlu mu isteğe bağlı mı: sınır gerçekte nerede oturuyor

PDFium Component'in uyguladığı kural nettir. Bir export, yokluğu bileşenin var olma nedeni olan işi yapamamasına neden olduğunda zorunludur ve yokluğu yalnızca bir yaprak özelliği kaldırdığında isteğe bağlıdır. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage zorunludur ve bunlarda gürültülü şekilde başarısız olmak doğrudur: render edemeyen bir görüntüleyici geriletilmiş bir görüntüleyici değildir, bozuk bir görüntüleyicidir

Bugün toleranslı yükleyici üzerinden erişilen her şey bir yapraktır. FPDFBookmark_GetColor, M109'dan sonra geldi ve yalnızca bir ana hat girdisinin isteğe bağlı /C renk dizisini sağlar, dolayısıyla ondan öncesine ait bir DLL basitçe ana hat rengi olmadığını bildirir. V8 yardımcıları FPDF_GetRecommendedV8Flags ve FPDF_GetArrayBufferAllocatorSharedInstance ve XFA dize yardımcıları FPDF_BStr_Init, FPDF_BStr_Set ve FPDF_BStr_Clear, yapı gereği herhangi bir V8-olmayan derlemede yoktur, dolayısıyla bunları zorunlu olarak ele almak sade pdfium.dll'i yüklenemez hâle getirirdi. Ve bu makaleyi motive eden çift: 2026-07-13'te yukarı akışta eklenen ve projenin DLLs/Win32 ve DLLs/Win64 altında gönderdiği dört PDFium ikilisinin derleme tarihinden daha yeni olan FPDFAttachment_SetDescription ve FPDFAttachment_GetDescription. Bu son durum, bir seferlik değil, problemin genel şeklidir: bir binding katmanı, sürekli hareket eden yukarı akış başlıklarını izler, kurulum programınızdaki DLL ise birisi onu her yeniden derlediğinde ayrık sıçramalarla hareket eder. Pascal tarafının, dağıtılan ikili dosyanın sahip olmadığı export'ları bildiği bir pencere her zaman vardır ve her yeni export'un zorunlu/isteğe bağlı çizgisinin hangi tarafına düştüğüne önceden karar vermek, o pencereyi hayatta kalınabilir tutan tek şeydir

FPDFDoc_GetAttachmentCount    := CheckGetProcAddress('FPDFDoc_GetAttachmentCount');
FPDFDoc_AddAttachment         := CheckGetProcAddress('FPDFDoc_AddAttachment');
FPDFAttachment_GetName        := CheckGetProcAddress('FPDFAttachment_GetName');
FPDFAttachment_GetStringValue := CheckGetProcAddress('FPDFAttachment_GetStringValue');
// Attachment descriptions were added after the bundled DLL revision.
// Keep them optional so older deployments continue to load.
FPDFAttachment_SetDescription := TryGetProcAddress('FPDFAttachment_SetDescription');
FPDFAttachment_GetDescription := TryGetProcAddress('FPDFAttachment_GetDescription');
FPDFAttachment_SetFile        := CheckGetProcAddress('FPDFAttachment_SetFile');
FPDFAttachment_GetFile        := CheckGetProcAddress('FPDFAttachment_GetFile');

Bir yetenek kapısı çağrı noktasında ne yapmalı?

Asimetrik olmalı ve o asimetri tüm tasarımdır. Çalışamayan bir okumanın dürüst bir boş cevabı vardır. Çalışamayan bir yazmanın hiç dürüst bir cevabı yoktur, dolayısıyla fırlatmalıdır. PDFium Component, ek açıklama özelliğini tam olarak o çizgi boyunca böler ve bu bölünme, eksik bir export'un sessiz veri kaybına dönüşmesini durduran şeydir. TPdf.GetAttachmentDescription, Assigned(FPDFAttachment_GetDescription)'ı test eder ve boş bir WString ile çıkar. Bu bir yalan değildir: export'u olmayan bir DLL'de, bileşen ekin gerçekten bir /Desc girdisi taşıyıp taşımadığını söyleyemez ve boş bir açıklama, hiç sahip olmamış bir ekle aynı şekilde okunur. Delphi'de PDF ekleriyle çalışma üzerine makalede ele alınan ek API'sinin geri kalanı, dokunulmadan çalışmaya devam eder

TPdf.SetAttachmentDescription ters yolu izler. Aynı Assigned testinde Check'i çağırır ve "Attachment descriptions are not supported by the loaded PDFium DLL" metniyle EPdfError fırlatır. Burada sessizce dönmek mevcut en kötü seçenek olurdu: çağıran bir açıklama ayarlar, hiç hata almaz, dosyayı kaydeder ve açıklamanın basitçe yok olduğu bir PDF gönderir. Aşağı akıştaki bir tüketici onun nereye gittiğini sorana kadar kimse fark etmez

function TPdf.GetAttachmentDescription(Index: Integer): WString;
begin
  CheckActive;
  Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
  Result := '';

  // Read side degrades: an old DLL cannot report /Desc, and '' is
  // indistinguishable from an attachment that carries no description.
  if not Assigned(FPDFAttachment_GetDescription) then
    Exit;
  // ... two-pass buffer sizing against FPDFAttachment_GetDescription ...
end;

procedure TPdf.SetAttachmentDescription(Index: Integer; const Value: WString);
begin
  CheckActive;
  Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
  // Write side refuses: silently dropping the value would produce a file
  // the caller believes carries a description and does not.
  Check(Assigned(FPDFAttachment_SetDescription),
    'Attachment descriptions are not supported by the loaded PDFium DLL');
  // ... FPDFDoc_GetAttachment, then FPDFAttachment_SetDescription ...
end;

Özelliği sunmadan önce yeteneği yoklamak

Bir istisna yakalamak, dağıtımınızın ne yapabildiğini keşfetmenin zayıf bir yoludur, dolayısıyla PDFium Component aynı testi adlandırılmış bir fonksiyon olarak sunar. AttachmentDescriptionFeaturesAvailable, LoadLibrary'yi çağırır ve çiftin her iki yarısının da çözülüp çözülmediğini döndürür. Kendi isteğe bağlı grupları için özdeş deseni izleyen V8FeaturesAvailable, XfaBStrHelpersAvailable ve XfaFeaturesAvailable'ın yanında oturur. Yoklamayı adlandırmak göründüğünden daha önemlidir: AttachmentDescriptionFeaturesAvailable adlı bir boolean, bir sonraki bakımcıya bu özelliğin dağıtılan ikiliye koşullu olduğunu söyler, ki bir özellik ayarlayıcısına gömülü çıplak bir Assigned testi bunu asla yapmaz. Ayrıca UI katmanına bağlanacak bir şey verir, dolayısıyla açıklama düzenleme kutusu kaydetmede kabul edip reddetmek yerine önceden devre dışı bırakılır

procedure TAttachmentFrame.SyncCapabilities;
begin
  // Ask once, at form setup, instead of discovering the limit on save.
  DescriptionEdit.Enabled := AttachmentDescriptionFeaturesAvailable;
  if not DescriptionEdit.Enabled then
    DescriptionEdit.TextHint := 'Requires a newer pdfium.dll';
end;

procedure TAttachmentFrame.SaveDescription(Pdf: TPdf; Index: Integer);
begin
  if not AttachmentDescriptionFeaturesAvailable then
    Exit;
  Pdf.AttachmentDescription[Index] := DescriptionEdit.Text;
end;

Binding kapsamı bir araçla neden kanıtlanmalı?

Çünkü sayılar bir insana güvenilebilecek noktanın ötesinde. PDFium Component, 21 genel PDFium başlığını 2026-07-29 yukarı akış referansına karşı denetledi ve 470 dışa aktarılmış C ABI fonksiyonu buldu. Binding zaten bunların 468'ini kapsıyordu. Kimse o iki fonksiyonluk boşluğu başlıkları okuyarak bulmadı; bir script bir saniyede buldu ve bir sonraki yukarı akış sıçramasında yine yapacak. tools/audit_pdfium_public_api.py kasıtlı olarak küçüktür: genel dizindeki her başlık boyunca FPDF_EXPORT ... FPDF_CALLCONV name('i regex ile eşleştirir, PDFium.pas'taki her CheckGetProcAddress('Name') ve TryGetProcAddress('Name')'i regex ile eşleştirir ve iki küme farkını yazdırır: bindingi olmayan export'lar için missing, export'u yukarı akışta artık var olmayan bindingler için stale. Herhangi bir küme boş değilse sıfır olmayan çıkar, dolayısıyla daha fazla törensellik olmadan bir derleme adımına düşer. Şu anki sonuç, 470'in 470'i bağlı, 0 eksik, 0 eskimiş

Eskimiş yön, eksik olan kadar hak eder. Yukarı akışın kaldırdığı bir export, gelecekteki her yüklemede sert başarısız olacak bir CheckGetProcAddress satırı bırakır ve bu tür çürüme, birisi DLL'i güncelleyene kadar görünmez. Manuel inceleme düşündüğünüz fonksiyonu bulur; düşünmediğinizi bulmaz. Ayrıca denetimin kasıtlı olarak her iki yükleyiciyi de kapsam olarak saydığını not edin, ki bu API sürüklenmesi için doğru karardır ve zorunlu/isteğe bağlı bölünmenin, satırı ekleyen kişinin yan ürünü yerine belgelenmiş bir karar olması gerektiğinin nedenidir

İsteğe bağlı bindingin dürüst olmayı bıraktığı yer

Açıkça belirtmeye değer iki sınır vardır, çünkü desen aşırı uygulanmaya müsaittir. İlki, bir nil fonksiyon işaretçisinin yalnızca ona dokunan gerçekten her yol önce Assigned'ı test ederse güvenli olduğudur. Yüzlerce cdecl fonksiyon değişkeni bildiren bir birimde, korunmasız tek bir çağrı, bir yığın izinde hiçbir anlamı olmayan bir adreste bir erişim ihlalidir. C sınırı boyunca çağırma kurallarını ve ömürleri yöneten aynı disiplin burada da geçerlidir ve PDFium bindingini ABI ve bellek güvenliği hatalarına karşı sertleştirme üzerine makalenin konusudur

İkinci sınır kapsamdır. İsteğe bağlı binding, her şeyi toleranslı yapmak için genel bir lisans değildir. FPDF_RenderPageBitmap isteğe bağlı olsaydı, bileşen memnuniyetle yüklenir ve sonra her sayfada başarısız olurdu, açık bir başlangıç hatasını, hiçbir belirgin nedeni olmayan bir çalışma zamanı hataları dağınıklığına dönüştürürdü. Zorunlu doğru varsayılandır. İsteğe bağlı, bir özelliğin gerçekten bir yaprak olduğu, yokluğun okuma tarafında savunulabilir geriletilmiş bir davranışı olduğu ve yazma tarafının nedeni adlandıran bir mesajla reddedebildiği zaman uzandığınız istisnadır

Burada anlatılan yükleyici tasarımı, yetenek yoklamaları ve denetim aracı, Delphi ve C++Builder için PDFium Component'in bir parçası olarak gönderilir; ürün sayfası, paketlenmiş PDFium ikililerini ve açığa vurduğu tam API yüzeyini listeler