pdfium.dll Anda dimuat dengan baik dan satu prosedur tetap hilang. PDFium Component menangani ini dengan memisahkan binding-nya menjadi dua kelas: export wajib yang diselesaikan lewat CheckGetProcAddress, yang membatalkan pemuatan sepenuhnya, dan export opsional yang diselesaikan lewat TryGetProcAddress, yang meninggalkan pointer nil dan pemeriksaan kapabilitas sebagai gantinya
Ini bukan masalah yang sama dengan DLL yang tidak ditemukan. Jika aplikasi Anda mati dengan error format EXE yang buruk, file yang hilang, atau ketidakcocokan arsitektur, cerita itu ada di artikel pendamping tentang deploy pdfium.dll dan mendiagnosis kegagalan pemuatan. Di sini loader-nya berhasil. Handle module-nya valid, ratusan export terselesaikan, dan jalannya masih berakhir sebelum halaman pertama Anda ter-render karena satu entry point yang tiba di build PDFium yang lebih baru tidak ada di binary di disk
Kenapa satu export yang hilang merusak seluruh pustaka?
Karena binding wajib adalah kontrak keras, dan diberlakukan selama satu urutan bind semua-atau-tidak-sama-sekali. PDFium Component menyelesaikan seluruh tabel export-nya di dalam LoadLibrary, satu pemanggilan CheckGetProcAddress demi yang lain. Hasil nil pertama memicu EPdfError dan memanggil UnloadLibrary sebelum itu, yang disengaja: bind parsial kalau tidak akan meninggalkan pointer yang sudah terselesaikan mengarah ke module yang segera dibebaskan, diam-diam mengalahkan setiap guard Assigned di hilir
Konsekuensinya adalah mode kegagalan yang membawa orang ke sini. Anda meng-upgrade komponen, mengirim pdfium.dll yang sama yang sudah Anda kirim selama dua tahun, dan aplikasinya tidak mau start. Errornya menamai export untuk fitur yang belum pernah Anda panggil. Tidak ada yang bisa Anda lakukan di call site yang membantu, karena call site itu tidak pernah berjalan; kegagalannya terjadi selama binding, sebelum dokumen apa pun dibuka
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;
Wajib atau opsional: di mana garisnya sebenarnya berada
Aturan yang diterapkan PDFium Component sederhana. Sebuah export wajib saat ketidakhadirannya membuat komponen tidak bisa melakukan pekerjaan yang menjadi alasan keberadaannya, dan opsional saat ketidakhadirannya hanya menghilangkan satu fitur daun. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage adalah wajib, dan gagal dengan jelas pada itu adalah benar: viewer yang tidak bisa me-render bukan viewer yang terdegradasi, itu viewer yang rusak
Semua yang dijangkau lewat loader toleran hari ini adalah daun. FPDFBookmark_GetColor tiba setelah M109 dan hanya menyediakan array warna /C opsional dari entri outline, jadi DLL yang lebih tua darinya sekadar melaporkan tidak ada warna bookmark. Helper V8 FPDF_GetRecommendedV8Flags dan FPDF_GetArrayBufferAllocatorSharedInstance, dan helper string XFA FPDF_BStr_Init, FPDF_BStr_Set dan FPDF_BStr_Clear, secara konstruksi tidak ada dari build non-V8 mana pun, jadi memperlakukannya sebagai wajib akan membuat pdfium.dll biasa tidak bisa dimuat. Dan pasangan yang memotivasi artikel ini: FPDFAttachment_SetDescription dan FPDFAttachment_GetDescription, ditambahkan di hulu pada 2026-07-13, lebih baru dari tanggal build keempat binary PDFium yang dikirim proyek di bawah DLLs/Win32 dan DLLs/Win64. Kasus terakhir itu adalah bentuk umum masalahnya, bukan kasus sekali saja: layer binding melacak header hulu, yang bergerak terus-menerus, sementara DLL di installer Anda bergerak dalam lompatan diskrit setiap kali seseorang membangunnya ulang. Selalu ada jendela di mana sisi Pascal tahu tentang export yang tidak dimiliki binary yang di-deploy, dan memutuskan lebih dulu sisi mana dari garis wajib/opsional yang dimasuki setiap export baru adalah satu-satunya yang membuat jendela itu bisa dilalui
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');
Apa yang seharusnya dilakukan capability gate di call site?
Ia harus asimetris, dan asimetri itu adalah keseluruhan desainnya. Pembacaan yang tidak bisa berjalan punya jawaban kosong yang jujur. Penulisan yang tidak bisa berjalan sama sekali tidak punya jawaban jujur, jadi ia harus memicu exception. PDFium Component memisahkan properti deskripsi attachment persis di garis itu, dan pemisahan itulah yang menghentikan export yang hilang berubah menjadi kehilangan data diam-diam. TPdf.GetAttachmentDescription menguji Assigned(FPDFAttachment_GetDescription) dan keluar dengan WString kosong. Itu bukan kebohongan: pada DLL tanpa export itu, komponen sungguh tidak bisa memastikan apakah attachment membawa entri /Desc, dan deskripsi kosong terbaca sama seperti attachment yang memang tidak pernah punya deskripsi. Sisa API attachment, dibahas di artikel tentang bekerja dengan lampiran PDF di Delphi, tetap berfungsi tanpa terpengaruh
TPdf.SetAttachmentDescription mengambil rute berlawanan. Ia memanggil Check pada test Assigned yang sama dan memicu EPdfError dengan teks "Attachment descriptions are not supported by the loaded PDFium DLL". Kembali dengan diam di sini akan menjadi pilihan terburuk yang ada: pemanggilnya akan mengatur deskripsi, tidak mendapat error, menyimpan file, dan mengirim PDF di mana deskripsinya sekadar tidak ada. Tidak ada yang menyadarinya sampai konsumen hilir bertanya ke mana perginya
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;
Menguji kapabilitas sebelum menawarkan fiturnya
Menangkap exception adalah cara yang buruk untuk mengetahui apa yang bisa dilakukan deployment Anda, jadi PDFium Component mengekspos test yang sama sebagai fungsi bernama. AttachmentDescriptionFeaturesAvailable memanggil LoadLibrary dan mengembalikan apakah kedua separuh pasangan itu terselesaikan. Ia duduk di samping V8FeaturesAvailable, XfaBStrHelpersAvailable dan XfaFeaturesAvailable, yang mengikuti pola identik untuk grup opsional mereka sendiri. Menamai probe itu lebih penting dari yang terlihat: boolean bernama AttachmentDescriptionFeaturesAvailable memberi tahu maintainer berikutnya bahwa fitur ini bersyarat pada binary yang di-deploy, sesuatu yang tidak pernah dilakukan test Assigned polos yang terkubur di dalam setter properti. Itu juga memberikan sesuatu bagi layer UI untuk di-bind, sehingga kotak edit deskripsi dinonaktifkan sejak awal alih-alih menerima input dan menolaknya saat disimpan
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;
Kenapa cakupan binding harus dibuktikan oleh alat?
Karena angkanya sudah melewati titik di mana manusia bisa dipercaya mengelolanya. PDFium Component mengaudit 21 header publik PDFium terhadap baseline hulu 2026-07-29 dan menemukan 470 fungsi ABI C yang di-export. Binding-nya sudah mencakup 468 di antaranya. Tidak ada yang menemukan celah dua itu dengan membaca header; sebuah skrip yang menemukannya, dalam satu detik, dan ia akan melakukannya lagi pada bump hulu berikutnya. tools/audit_pdfium_public_api.py sengaja dibuat kecil: ia mencocokkan regex FPDF_EXPORT ... FPDF_CALLCONV name( lintas setiap header di direktori publik, mencocokkan regex setiap CheckGetProcAddress('Name') dan TryGetProcAddress('Name') di PDFium.pas, dan mencetak dua selisih himpunan: missing untuk export tanpa binding, stale untuk binding yang export-nya sudah tidak ada lagi di hulu. Ia keluar dengan non-zero saat salah satu himpunan tidak kosong, jadi ia masuk ke langkah build tanpa seremoni lebih lanjut. Hasil saat ini adalah 470 dari 470 terikat, missing 0, stale 0
Arah stale-nya sama berharganya dengan arah missing-nya. Export yang dihapus hulu meninggalkan baris CheckGetProcAddress yang akan gagal keras pada setiap pemuatan masa depan, dan jenis kebusukan itu tidak terlihat sampai suatu hari seseorang memperbarui DLL-nya. Review manual menemukan fungsi yang sedang Anda pikirkan; ia tidak menemukan yang tidak sedang Anda pikirkan. Perhatikan juga bahwa audit ini sengaja menghitung kedua loader sebagai cakupan, yang merupakan keputusan tepat untuk drift API dan alasan kenapa pemisahan wajib/opsional harus jadi keputusan terdokumentasi, bukan efek samping dari siapa pun yang menambahkan baris itu
Di mana binding opsional berhenti bersikap jujur
Dua batasan layak dinyatakan secara jelas, karena polanya mudah diterapkan berlebihan. Yang pertama adalah pointer fungsi nil hanya aman jika betul-betul setiap jalur yang menyentuhnya menguji Assigned lebih dulu. Dalam unit yang mendeklarasikan ratusan variabel fungsi cdecl, satu pemanggilan tanpa guard adalah access violation pada alamat yang tidak berarti apa pun dalam stack trace. Disiplin yang sama yang mengatur calling convention dan lifetime lintas batas C berlaku di sini, dan itu subjek dari artikel tentang memperkuat binding PDFium terhadap kesalahan ABI dan keamanan memori
Batasan kedua adalah cakupan. Binding opsional bukan lisensi umum untuk membuat segalanya toleran. Jika FPDF_RenderPageBitmap opsional, komponen akan dimuat dengan senang hati lalu gagal pada setiap halaman, mengubah satu error startup yang jelas menjadi sebaran error runtime tanpa penyebab yang jelas. Wajib adalah default yang benar. Opsional adalah pengecualian yang Anda raih saat sebuah fitur sungguh adalah daun, saat ketidakhadirannya punya perilaku terdegradasi yang bisa dipertanggungjawabkan di sisi baca, dan saat sisi tulis bisa menolak dengan pesan yang menamai alasannya
Desain loader, probe kapabilitas dan alat audit yang dijelaskan di sini hadir sebagai bagian dari PDFium Component untuk Delphi dan C++Builder; halaman produknya mendaftar binary PDFium yang dibundel dan seluruh permukaan API yang mereka ekspos