pdfium.dll-ul dumneavoastră se încarcă bine, iar o procedură tot lipsește. PDFium Component gestionează asta împărțind binding-urile sale în două clase: exporturile necesare rezolvate prin CheckGetProcAddress, care abandonează încărcarea direct, și exporturile opționale rezolvate prin TryGetProcAddress, care lasă în urmă un pointer nil și o verificare de capabilitate în schimb
Aceasta nu este aceeași problemă ca un DLL care nu poate fi găsit. Dacă aplicația dumneavoastră moare cu o eroare de format EXE greșit, un fișier lipsă sau o nepotrivire de arhitectură, acea poveste este spusă în articolul asociat despre implementarea pdfium.dll și diagnosticarea eșecurilor de încărcare. Aici loader-ul a reușit. Handle-ul modulului este valid, sute de exporturi s-au rezolvat, iar rularea tot se termină înainte ca prima dumneavoastră pagină să se randeze pentru că un punct de intrare care a sosit într-un build PDFium mai nou nu se află în binarul de pe disc
De ce un singur export lipsă strică întreaga bibliotecă?
Pentru că un binding necesar este un contract dur, și este aplicat în timpul unei singure secvențe de bind totul-sau-nimic. PDFium Component își rezolvă întregul tabel de exporturi în interiorul lui LoadLibrary, un apel CheckGetProcAddress după altul. Primul rezultat nil ridică EPdfError și apelează UnloadLibrary înainte de asta, ceea ce este deliberat: un bind parțial ar lăsa altfel pointeri deja rezolvați îndreptați spre un modul care este pe cale să fie eliberat, învingând tacit fiecare gardă Assigned din aval
Consecința este modul de eșec care aduce oamenii aici. Actualizați componenta, livrați același pdfium.dll pe care l-ați livrat de doi ani, iar aplicația nu pornește. Eroarea numește un export pentru o funcționalitate pe care nu ați apelat-o niciodată. Nimic ce faceți la locul apelului nu ajută, deoarece locul apelului nu rulează niciodată; eșecul a avut loc în timpul bind-ului, înainte ca vreun document să fie deschis
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;
Necesar sau opțional: unde stă de fapt linia
Regula pe care PDFium Component o aplică este directă. Un export este necesar când absența lui face ca componenta să nu poată face treaba pentru care există, și opțional când absența lui elimină doar o funcționalitate frunză. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage sunt necesare, iar eșecul zgomotos pe acestea este corect: un vizualizator care nu poate randa nu este un vizualizator degradat, este unul stricat
Tot ce este atins astăzi prin loader-ul tolerant este o frunză. FPDFBookmark_GetColor a sosit după M109 și furnizează doar array-ul opțional de culoare /C al unei intrări de contur, așa că un DLL care îl predatează pur și simplu raportează nicio culoare de semn de carte. Ajutoarele V8 FPDF_GetRecommendedV8Flags și FPDF_GetArrayBufferAllocatorSharedInstance, și ajutoarele de string XFA FPDF_BStr_Init, FPDF_BStr_Set și FPDF_BStr_Clear, sunt absente din orice build non-V8 prin construcție, așa că tratarea lor ca necesare ar face pdfium.dll-ul simplu de neîncărcat. Iar perechea care a motivat acest articol: FPDFAttachment_SetDescription și FPDFAttachment_GetDescription, adăugate în amonte pe 2026-07-13, mai târziu decât data de build a tuturor celor patru binare PDFium pe care proiectul le livrează sub DLLs/Win32 și DLLs/Win64. Acel ultim caz este forma generală a problemei, nu un caz izolat: un strat de binding urmărește anteturile din amonte, care se mișcă continuu, în timp ce DLL-ul din instalatorul dumneavoastră se mișcă în salturi discrete ori de câte ori cineva îl reconstruiește. Există întotdeauna o fereastră în care partea Pascal cunoaște exporturi pe care binarul implementat nu le are, iar decizia dinainte asupra căreia parte a liniei necesar/opțional cade fiecare export nou este singurul lucru care menține acea fereastră supraviețuibilă
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');
Ce ar trebui să facă o poartă de capabilitate la locul apelului?
Ar trebui să fie asimetrică, iar acea asimetrie este întregul design. O citire care nu poate rula are un răspuns onest gol. O scriere care nu poate rula nu are deloc un răspuns onest, așa că trebuie să ridice o excepție. PDFium Component împarte proprietatea descrierii atașamentului exact pe acea linie, iar împărțirea este ceea ce oprește un export lipsă să se transforme în pierdere tacită de date. TPdf.GetAttachmentDescription testează Assigned(FPDFAttachment_GetDescription) și iese cu un WString gol. Aceasta nu este o minciună: pe un DLL fără exportul respectiv, componenta chiar nu poate spune dacă atașamentul poartă o intrare /Desc, iar o descriere goală se citește la fel ca un atașament care nu a avut niciodată una. Restul API-ului de atașamente, tratat în articolul despre lucrul cu atașamente PDF în Delphi, continuă să funcționeze neatins
TPdf.SetAttachmentDescription ia ruta opusă. Apelează Check pe același test Assigned și ridică EPdfError cu textul "Attachment descriptions are not supported by the loaded PDFium DLL". Revenirea tacită aici ar fi cea mai proastă opțiune disponibilă: apelantul ar seta o descriere, nu ar primi nicio eroare, ar salva fișierul și ar livra un PDF unde descrierea este pur și simplu absentă. Nimeni nu observă până când un consumator din aval întreabă unde s-a dus
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;
Sondarea capabilității înainte de a oferi funcționalitatea
Prinderea unei excepții este un mod slab de a descoperi ce poate face implementarea dumneavoastră, așa că PDFium Component expune același test ca o funcție numită. AttachmentDescriptionFeaturesAvailable apelează LoadLibrary și returnează dacă ambele jumătăți ale perechii s-au rezolvat. Stă alături de V8FeaturesAvailable, XfaBStrHelpersAvailable și XfaFeaturesAvailable, care urmează același tipar pentru propriile lor grupuri opționale. Numirea sondei contează mai mult decât pare: un boolean numit AttachmentDescriptionFeaturesAvailable spune următorului mentenant că această funcționalitate este condiționată de binarul implementat, ceea ce un simplu test Assigned îngropat într-un setter de proprietate nu face niciodată. Mai dă și stratului de UI ceva de care să se lege, astfel încât căsuța de editare a descrierii este dezactivată din start, în loc să accepte intrare și să o respingă la salvare
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;
De ce trebuie dovedită acoperirea binding-ului printr-un instrument?
Pentru că numerele au trecut de punctul în care unui om i se poate avea încredere cu ele. PDFium Component a auditat 21 de anteturi PDFium publice față de o linie de bază din amonte din 2026-07-29 și a găsit 470 de funcții ABI C exportate. Binding-ul acoperea deja 468 dintre ele. Nimeni nu a localizat acel gol de doi citind anteturile; un script a făcut-o, într-o secundă, și o va face din nou la următoarea actualizare din amonte. tools/audit_pdfium_public_api.py este deliberat mic: se potrivește cu regex pe FPDF_EXPORT ... FPDF_CALLCONV name( peste fiecare antet din directorul public, se potrivește cu regex pe fiecare CheckGetProcAddress('Name') și TryGetProcAddress('Name') din PDFium.pas, și afișează cele două diferențe de mulțimi: missing pentru exporturile fără niciun binding, stale pentru binding-urile al căror export nu mai există în amonte. Iese cu cod diferit de zero când oricare mulțime este nevidă, așa că se strecoară într-un pas de build fără mai multă ceremonie. Rezultatul curent este 470 din 470 legate, missing 0, stale 0
Direcția stale își câștigă locul la fel de mult ca cea missing. Un export pe care amontele îl elimină lasă în urmă o linie CheckGetProcAddress care va eșua dur fiecare încărcare viitoare, iar acel tip de putrezire este invizibil până în ziua în care cineva actualizează DLL-ul. Revizia manuală găsește funcția la care vă gândeați; nu o găsește pe cea la care nu vă gândeați. Rețineți de asemenea că auditul contabilizează deliberat ambii loaderi ca acoperire, ceea ce este alegerea corectă pentru deriva API, și motivul pentru care împărțirea necesar/opțional trebuie să fie o decizie documentată, nu un produs secundar al oricui a adăugat linia
Unde încetează binding-ul opțional să fie onest
Două limite merită enunțate clar, deoarece tiparul este ușor de aplicat excesiv. Prima este că un pointer de funcție nil este sigur doar dacă literalmente fiecare cale care îl atinge testează Assigned mai întâi. Într-o unitate care declară sute de variabile de funcție cdecl, un singur apel nepăzit este o încălcare de acces la o adresă care nu înseamnă nimic într-un stack trace. Aceeași disciplină care guvernează convențiile de apel și duratele de viață peste granița C se aplică aici, și este subiectul articolului despre întărirea binding-ului PDFium împotriva defectelor ABI și de siguranță a memoriei
A doua limită este domeniul de aplicare. Binding-ul opțional nu este o licență generală de a face totul tolerant. Dacă FPDF_RenderPageBitmap ar fi opțional, componenta s-ar încărca fericită și apoi ar eșua pe fiecare pagină, convertind o eroare clară de pornire într-o împrăștiere de erori la rulare fără nicio cauză evidentă. Necesar este valoarea implicită corectă. Opțional este excepția la care apelați când o funcționalitate este cu adevărat o frunză, când absența are un comportament degradat apărabil pe partea de citire, și când partea de scriere poate refuza cu un mesaj care numește motivul
Designul loader-ului, sondele de capabilitate și instrumentul de audit descrise aici vin ca parte a PDFium Component pentru Delphi și C++Builder; pagina de produs listează binarele PDFium incluse și întreaga suprafață API pe care o expun