pdfium.dll-en din laster fint, og én prosedyre mangler fortsatt. PDFium Component håndterer dette ved å dele bindingene sine i to klasser: påkrevde eksporter løst gjennom CheckGetProcAddress, som avbryter innlastingen helt, og valgfrie eksporter løst gjennom TryGetProcAddress, som etterlater en nil-peker og en kapabilitetssjekk i stedet
Dette er ikke samme problem som en DLL som ikke kan finnes. Hvis applikasjonen din dør med en feil om ugyldig EXE-format, en manglende fil, eller et arkitekturmisforhold, fortelles den historien i følgeartikkelen om utrulling av pdfium.dll og diagnostisering av innlastingsfeil. Her lyktes lasteren. Modulhåndtaket er gyldig, hundrevis av eksporter ble løst, og kjøringen ender likevel før første side rendres fordi ett inngangspunkt som ankom i et nyere PDFium-bygg ikke er i binærfilen på disk
Hvorfor ødelegger én manglende eksport hele biblioteket?
Fordi en påkrevd binding er en hard kontrakt, og den håndheves under én alt-eller-ingenting bindingssekvens. PDFium Component løser hele eksporttabellen sin inne i LoadLibrary, ett CheckGetProcAddress-kall etter det andre. Det første nil-resultatet kaster EPdfError og kaller UnloadLibrary før det gjør det, som er tilsiktet: en delvis binding ville ellers etterlate allerede løste pekere rettet inn i en modul som er i ferd med å frigjøres, og stille beseire hver Assigned-vakt nedstrøms
Konsekvensen er feilmodusen som bringer folk hit. Du oppgraderer komponenten, sender samme pdfium.dll du har sendt i to år, og applikasjonen starter ikke. Feilen navngir en eksport for en funksjon du aldri har kalt. Ingenting du gjør ved kallestedet hjelper, fordi kallestedet aldri kjører; feilen skjedde under bindingen, før noe dokument ble åpnet
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;
Påkrevd eller valgfritt: hvor grensen egentlig går
Regelen PDFium Component anvender er butt. En eksport er påkrevd når dens fravær gjør komponenten ute av stand til å gjøre jobben den finnes for å gjøre, og valgfri når fraværet bare fjerner én bladfunksjon. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage er påkrevde, og å feile høylytt på dem er korrekt: en viser som ikke kan rendre er ikke en degradert viser, den er ødelagt
Alt nådd gjennom den tolerante lasteren i dag er et blad. FPDFBookmark_GetColor ankom etter M109 og leverer bare det valgfrie /C-fargearrayet til en disposisjonsoppføring, så en DLL som kommer fra før den rapporterer ganske enkelt ingen bokmerkefarge. V8-hjelperne FPDF_GetRecommendedV8Flags og FPDF_GetArrayBufferAllocatorSharedInstance, og XFA-strenghjelperne FPDF_BStr_Init, FPDF_BStr_Set og FPDF_BStr_Clear, er fraværende fra ethvert ikke-V8-bygg av konstruksjon, så å behandle dem som påkrevde ville gjøre den vanlige pdfium.dll-en ulastbar. Og paret som motiverte denne artikkelen: FPDFAttachment_SetDescription og FPDFAttachment_GetDescription, lagt til oppstrøms 2026-07-13, senere enn byggedatoen til alle fire PDFium-binærfiler prosjektet leverer under DLLs/Win32 og DLLs/Win64. Det siste tilfellet er den generelle formen av problemet, ikke en engangshendelse: et bindingslag sporer oppstrøms-headere, som beveger seg kontinuerlig, mens DLL-en i installatøren din beveger seg i diskrete sprang hver gang noen bygger den på nytt. Det er alltid et vindu hvor Pascal-siden vet om eksporter den utrullede binærfilen ikke har, og å bestemme på forhånd hvilken side av påkrevd/valgfri-grensen hver ny eksport faller på er det eneste som holder det vinduet overlevbart
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');
Hva bør en kapabilitetsport gjøre ved kallestedet?
Den bør være asymmetrisk, og den asymmetrien er hele designet. En lesing som ikke kan kjøre har et ærlig tomt svar. En skriving som ikke kan kjøre har ikke noe ærlig svar i det hele tatt, så den må kaste et unntak. PDFium Component deler egenskapen for vedleggsbeskrivelse nøyaktig langs den linjen, og delingen er det som stopper en manglende eksport fra å bli stille datatap. TPdf.GetAttachmentDescription tester Assigned(FPDFAttachment_GetDescription) og avslutter med en tom WString. Det er ikke en løgn: på en DLL uten eksporten kan komponenten genuint ikke si om vedlegget bærer en /Desc-oppføring, og en tom beskrivelse leses samme vei som et vedlegg som aldri hadde en. Resten av vedlegg-API-et, dekket i artikkelen om arbeid med PDF-vedlegg i Delphi, fortsetter å fungere urørt
TPdf.SetAttachmentDescription tar motsatt vei. Den kaller Check på samme Assigned-test og kaster EPdfError med teksten «Attachment descriptions are not supported by the loaded PDFium DLL». Å returnere stille her ville vært det verste tilgjengelige alternativet: den som kaller ville sette en beskrivelse, få ingen feil, lagre filen, og sende ut en PDF hvor beskrivelsen ganske enkelt er fraværende. Ingen legger merke til det før en nedstrøms-forbruker spør hvor den ble av
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;
Å prøve kapabiliteten før du tilbyr funksjonen
Å fange et unntak er en dårlig måte å oppdage hva utrullingen din kan gjøre, så PDFium Component eksponerer samme test som en navngitt funksjon. AttachmentDescriptionFeaturesAvailable kaller LoadLibrary og returnerer om begge halvdeler av paret ble løst. Den sitter ved siden av V8FeaturesAvailable, XfaBStrHelpersAvailable og XfaFeaturesAvailable, som følger det identiske mønsteret for sine egne valgfrie grupper. Å navngi proben betyr mer enn det ser ut til: en boolean kalt AttachmentDescriptionFeaturesAvailable forteller neste vedlikeholder at denne funksjonen er betinget av den utrullede binærfilen, noe en bar Assigned-test begravet i en egenskapssetter aldri gjør. Den gir også UI-laget noe å binde seg til, slik at beskrivelses-redigeringsboksen deaktiveres på forhånd i stedet for å godta inndata og avvise den ved lagring
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;
Hvorfor må bindingsdekning bevises av et verktøy?
Fordi tallene er forbi punktet hvor et menneske kan betros dem. PDFium Component reviderte 21 offentlige PDFium-headere mot en 2026-07-29 oppstrøms-basislinje og fant 470 eksporterte C ABI-funksjoner. Bindingen dekket allerede 468 av dem. Ingen lokaliserte det gapet på to ved å lese headere; et skript gjorde det, på et sekund, og det vil gjøre det igjen ved neste oppstrøms-oppgradering. tools/audit_pdfium_public_api.py er bevisst liten: den regex-matcher FPDF_EXPORT ... FPDF_CALLCONV name( på tvers av hver header i den offentlige mappen, regex-matcher hver CheckGetProcAddress('Name') og TryGetProcAddress('Name') i PDFium.pas, og skriver ut de to mengdedifferansene: missing for eksporter uten binding, stale for bindinger hvis eksport ikke lenger finnes oppstrøms. Den avslutter med kode ulik null når en av mengdene ikke er tom, så den faller inn i et byggesteg uten videre seremoni. Gjeldende resultat er 470 av 470 bundet, mangler 0, foreldet 0
Foreldet-retningen fortjener sin plass like mye som manglende-retningen. En eksport oppstrøms fjerner etterlater en CheckGetProcAddress-linje som vil hardfeile hver fremtidig innlasting, og den typen forråtnelse er usynlig inntil dagen noen oppdaterer DLL-en. Manuell gjennomgang finner funksjonen du tenkte på; den finner ikke den du ikke tenkte på. Merk også at revisjonen bevisst teller begge lasterne som dekning, som er det riktige valget for API-drift og grunnen til at påkrevd/valgfri-delingen må være en dokumentert beslutning snarere enn et biprodukt av hvem som la til linjen
Hvor valgfri binding slutter å være ærlig
To grenser er verdt å nevne tydelig, fordi mønsteret er lett å overanvende. Den første er at en nil-funksjonspeker bare er trygg hvis bokstavelig talt hver sti som rører den tester Assigned først. I en enhet som erklærer hundrevis av cdecl-funksjonsvariabler er ett enkelt uvoktet kall et tilgangsbrudd på en adresse som betyr ingenting i en stakksporing. Samme disiplin som styrer kallekonvensjoner og levetider på tvers av C-grensen gjelder her, og det er temaet for artikkelen om herding av PDFium-bindingen mot ABI- og minnesikkerhetsfeil
Den andre grensen er omfang. Valgfri binding er ikke en generell lisens til å gjøre alt tolerant. Hvis FPDF_RenderPageBitmap var valgfri, ville komponenten laste lykkelig og deretter feile på hver side, og gjøre om én klar oppstartsfeil til en spredning av kjøretidsfeil uten opplagt årsak. Påkrevd er standarden. Valgfri er unntaket du griper til når en funksjon genuint er et blad, når fraværet har en forsvarlig degradert oppførsel på lesesiden, og når skrivesiden kan avvise med en melding som navngir årsaken
Lasterdesignet, kapabilitetsprobene og revisjonsverktøyet beskrevet her leveres som del av PDFium Component for Delphi og C++Builder; produktsiden lister de medfølgende PDFium-binærfilene og hele API-flaten de eksponerer