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

PDFium Component, Delphi dışa aktarma tablosunu tek geçişte bağlar; CheckGetProcAddress eksik zorunlu bir dışa aktarmada yüklemeyi durdururken TryGetProcAddress isteğe bağlı olanı güvenle düşürür
Zorunlu dışa aktarmalar her şey-ya-da-hiç bağlanır ve ilk nil'de yüklemeyi durdurur; isteğe bağlı dışa aktarmalar ise bir Assigned yetenek kontrolünün ardında nil işaretçi bırakır
function CheckGetProcAddress(const Name: string): Pointer;
begin
  Result := GetProcAddress(PDFiumLibrary, PChar(Name));
  if Result = nil then
  begin
    // Eksik bir zorunlu dışa aktarım, dağıtılmış pdfium.dll'nin bu bağlamanın yapısından
    // daha eski olduğu anlamına gelir. Şimdiye kadar çözülen her işaretçiyi bırakın
    // böylece hiçbir çağıran, serbest bırakmak üzere olduğumuz modüle erişemez.
    UnloadLibrary;
    raise EPdfError.Create('Required PDFium export not found: ' + Name);
  end;
end;

function TryGetProcAddress(const Name: string): Pointer;
begin
  // İsteğe bağlı dışa aktarım. nil burada meşru bir yanıttır; her çağıran,
  // değişkenin değerini okumadan önce Assigned() testini yapmak zorundadır.
  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');
// Ek açıklamaları, paketlenen DLL revizyonundan sonra eklendi.
// Eski dağıtımların yüklenmeye devam etmesi için onları isteğe bağlı tutun.
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

Delphi'de eksik bir PDFium eki tanımı dışa aktarımı TPdf.GetAttachmentDescription üzerinden boş bir okuma döndürür ve yazıda istisna yükseltir; AttachmentDescriptionFeaturesAvailable ile kapılanır
Okuma tarafı boş bir yanıta geriler; yazma tarafı adlandırılmış bir nedenle yükselir ve adlandırılmış bir sonda, UI'nin özelliği baştan devre dışı bırakmasını sağlar
function TPdf.GetAttachmentDescription(Index: Integer): WString;
begin
  CheckActive;
  Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
  Result := '';

  // Okuma tarafı gerilemesi: eski bir DLL /Desc raporlayamaz ve '' değeri,
  // açıklama taşımayan bir ekten ayırt edilemez.
  if not Assigned(FPDFAttachment_GetDescription) then
    Exit;
  // ... FPDFAttachment_GetDescription'a karşı iki geçişli arabellek boyutlandırma ...
end;

procedure TPdf.SetAttachmentDescription(Index: Integer; const Value: WString);
begin
  CheckActive;
  Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
  // Yazma tarafı reddeder: değeri sessizce düşürmek, çağıranın açıklama taşıdığına inandığı
  // ancak taşımayan bir dosya üretirdi.
  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
  // Kaydetme sırasında sınırı keşfetmek yerine, form kurulumunda bir kez sorun.
  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