Din pdfium.dll laddas utan problem och en procedur saknas ändå. PDFium Component hanterar detta genom att dela sina bindningar i två klasser: obligatoriska exporter upplösta genom CheckGetProcAddress, som avbryter laddningen rakt av, och valfria exporter upplösta genom TryGetProcAddress, som lämnar en nil-pekare och en förmågekontroll bakom sig i stället
Det här är inte samma problem som en DLL som inte kan hittas. Om din applikation dör med ett fel om felaktigt EXE-format, en saknad fil, eller en arkitekturmissmatch, den historien berättas i följdartikeln om att driftsätta pdfium.dll och diagnostisera laddningsfel. Här lyckades laddaren. Modulhandtaget är giltigt, hundratals exporter löstes upp, och körningen slutar ändå innan din första sida renderas eftersom en ingångspunkt som anlände i en nyare PDFium-bygge inte finns i binärfilen på disk
Varför bryter en saknad export hela biblioteket?
Eftersom en obligatorisk bindning är ett hårt kontrakt, och det upprätthålls under en enda allt-eller-inget-bindningssekvens. PDFium Component löser upp hela sin exporttabell inuti LoadLibrary, ett CheckGetProcAddress-anrop efter ett annat. Det första nil-resultatet kastar EPdfError och anropar UnloadLibrary innan det gör det, vilket är avsiktligt: en partiell bindning skulle annars lämna redan upplösta pekare siktade in i en modul som är på väg att frigöras, och tyst besegra varje Assigned-vakt nedströms
Konsekvensen är felläget som tar folk hit. Du uppgraderar komponenten, levererar samma pdfium.dll du levererat i två år, och applikationen startar inte. Felet namnger en export för en funktion du aldrig anropat. Ingenting du gör vid anropsstället hjälper, eftersom anropsstället aldrig körs; felet skedde under bindning, innan något dokument öppnades
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;
Obligatorisk eller valfri: var linjen faktiskt går
Regeln PDFium Component tillämpar är rättfram. En export är obligatorisk när dess frånvaro gör komponenten oförmögen att göra jobbet den existerar för, och valfri när dess frånvaro bara tar bort en enda lövfunktion. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage är obligatoriska, och att misslyckas högljutt på de är korrekt: en visare som inte kan rendera är inte en degraderad visare, den är trasig
Allt som nås genom den toleranta laddaren idag är ett löv. FPDFBookmark_GetColor anlände efter M109 och tillhandahåller bara den valfria /C-färgarrayen i en dispositionspost, så en DLL som föregår den rapporterar helt enkelt ingen bokmärkesfärg. V8-hjälparna FPDF_GetRecommendedV8Flags och FPDF_GetArrayBufferAllocatorSharedInstance, och XFA-strängshjälparna FPDF_BStr_Init, FPDF_BStr_Set och FPDF_BStr_Clear, är frånvarande från varje icke-V8-bygge genom konstruktion, så att behandla dem som obligatoriska skulle göra den rena pdfium.dll:n oladdningsbar. Och paret som motiverade den här artikeln: FPDFAttachment_SetDescription och FPDFAttachment_GetDescription, tillagda uppströms 2026-07-13, senare än byggdatumet för alla fyra PDFium-binärfilerna projektet levererar under DLLs/Win32 och DLLs/Win64. Det sista fallet är den generella formen på problemet, inte en engångsföreteelse: ett bindningslager spårar uppströms-headers, som rör sig kontinuerligt, medan DLL:n i din installerare rör sig i diskreta hopp närhelst någon bygger om den. Det finns alltid ett fönster där Pascal-sidan känner till exporter den driftsatta binärfilen inte har, och att bestämma i förväg vilken sida av obligatorisk/valfri-linjen varje ny export faller på är det enda som håller det fönstret överlevbart
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');
Vad bör en förmågegrind göra vid anropsstället?
Den bör vara asymmetrisk, och den asymmetrin är hela designen. En läsning som inte kan köras har ett ärligt tomt svar. En skrivning som inte kan köras har inget ärligt svar alls, så den måste kasta. PDFium Component delar upp bilageskrivningsegenskapen exakt längs den linjen, och uppdelningen är vad som stoppar en saknad export från att förvandlas till tyst dataförlust. TPdf.GetAttachmentDescription testar Assigned(FPDFAttachment_GetDescription) och avslutar med en tom WString. Det är inte en lögn: på en DLL utan exporten kan komponenten genuint inte avgöra om bilagan bär en /Desc-post, och en tom beskrivning läses samma sätt som en bilaga som aldrig hade en. Resten av bilage-API:et, täckt i artikeln om att arbeta med PDF-bilagor i Delphi, fortsätter fungera orört
TPdf.SetAttachmentDescription tar den motsatta vägen. Den anropar Check på samma Assigned-test och kastar EPdfError med texten "Attachment descriptions are not supported by the loaded PDFium DLL". Att returnera tyst här skulle vara det sämsta tillgängliga alternativet: anroparen skulle sätta en beskrivning, få inget fel, spara filen, och leverera en PDF där beskrivningen helt enkelt saknas. Ingen märker det förrän en nedströms-konsument frågar var den tog vägen
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;
Att sondera förmågan innan du erbjuder funktionen
Att fånga ett undantag är ett dåligt sätt att upptäcka vad din driftsättning kan göra, så PDFium Component exponerar samma test som en namngiven funktion. AttachmentDescriptionFeaturesAvailable anropar LoadLibrary och returnerar om båda halvorna av paret löstes upp. Den sitter bredvid V8FeaturesAvailable, XfaBStrHelpersAvailable och XfaFeaturesAvailable, som följer det identiska mönstret för sina egna valfria grupper. Att namnge sonden spelar större roll än det ser ut: en boolean kallad AttachmentDescriptionFeaturesAvailable talar om för nästa underhållare att den här funktionen är villkorad på den driftsatta binärfilen, vilket ett rent Assigned-test begravt i en egenskapssättare aldrig gör. Det ger också UI-lagret något att binda till, så att beskrivningsredigeringsrutan inaktiveras i förväg snarare än att acceptera indata och avvisa den vid sparning
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;
Varför måste bindningstäckning bevisas av ett verktyg?
Eftersom siffrorna är bortom punkten där en människa kan betros med dem. PDFium Component granskade 21 publika PDFium-headers mot en uppströms-baslinje från 2026-07-29 och hittade 470 exporterade C ABI-funktioner. Bindningen täckte redan 468 av dem. Ingen lokaliserade det gapet på två genom att läsa headers; ett skript gjorde det, på en sekund, och det kommer göra det igen vid nästa uppströms-hopp. tools/audit_pdfium_public_api.py är avsiktligt litet: det regex-matchar FPDF_EXPORT ... FPDF_CALLCONV name( över varje header i den publika katalogen, regex-matchar varje CheckGetProcAddress('Name') och TryGetProcAddress('Name') i PDFium.pas, och skriver ut de två mängdskillnaderna: missing för exporter utan bindning, stale för bindningar vars export inte längre finns uppströms. Det avslutas med icke-noll när endera mängden är icke-tom, så det passar in i ett byggsteg utan ytterligare ceremoni. Det aktuella resultatet är 470 av 470 bundna, 0 saknade, 0 föråldrade
Föråldrad-riktningen förtjänar sin plats lika mycket som den saknade. En export uppströms tar bort lämnar en CheckGetProcAddress-rad bakom sig som kommer hårdmisslyckas varje framtida laddning, och den sortens röta är osynlig tills dagen någon uppdaterar DLL:n. Manuell granskning hittar funktionen du tänkte på; den hittar inte den du inte tänkte på. Notera också att granskningen avsiktligt räknar båda laddarna som täckning, vilket är rätt beslut för API-drift och anledningen till att obligatorisk/valfri-uppdelningen måste vara ett dokumenterat beslut snarare än en biprodukt av vem som råkade lägga till raden
Var valfri bindning slutar vara ärlig
Två gränser är värda att uttrycka rakt på sak, eftersom mönstret är lätt att överanvända. Den första är att en nil-funktionspekare bara är säker om bokstavligen varje väg som rör den testar Assigned först. I en enhet som deklarerar hundratals cdecl-funktionsvariabler är ett enda oskyddat anrop en åtkomstöverträdelse vid en adress som betyder ingenting i en stackspårning. Samma disciplin som styr anropskonventioner och livstider över C-gränsen gäller här, och den är ämnet för artikeln om att härda PDFium-bindningen mot ABI- och minnessäkerhetsfel
Den andra gränsen är omfattning. Valfri bindning är inte en generell licens att göra allt tolerant. Om FPDF_RenderPageBitmap vore valfri skulle komponenten ladda glatt och sedan misslyckas på varje sida, och omvandla ett tydligt startfel till en spridning av körtidsfel utan uppenbar orsak. Obligatorisk är rätt standard. Valfri är undantaget du sträcker dig efter när en funktion genuint är ett löv, när frånvaron har ett försvarbart degraderat beteende på läsidan, och när skrivsidan kan vägra med ett meddelande som namnger anledningen
Laddardesignen, förmågesonderna och granskningsverktyget beskrivet här levereras som del av PDFium Component för Delphi och C++Builder; produktsidan listar de medföljande PDFium-binärfilerna och den fullständiga API-ytan de exponerar