Vaš pdfium.dll se pravilno naloži, en postopek pa še vedno manjka. PDFium Component to obravnava tako, da svoje vezave razdeli v dva razreda: obvezni izvozi, razrešeni prek CheckGetProcAddress, ki nalaganje povsem prekinejo, in neobvezni izvozi, razrešeni prek TryGetProcAddress, ki namesto tega pustijo nil kazalec in preverjanje zmogljivosti
To ni isti problem kot DLL, ki ga ni mogoče najti. Če vaša aplikacija umre z napako slabe oblike EXE, manjkajočo datoteko ali neujemanjem arhitekture, je ta zgodba povedana v spremljevalnem članku o namestitvi pdfium.dll in diagnosticiranju napak nalaganja. Tukaj je nalagalnik uspel. Ročnik modula je veljaven, sto izvozov je razrešenih, zagon pa se vseeno konča, preden se izriše vaša prva stran, ker ene vstopne točke, ki je prišla z novejšo gradnjo PDFium, ni v binarni datoteki na disku
Zakaj en manjkajoč izvoz pokvari celotno knjižnico?
Ker je obvezna vezava trda pogodba, uveljavljena med enim samim zaporedjem vse-ali-nič vezave. PDFium Component razreši celotno svojo izvozno tabelo znotraj LoadLibrary, en klic CheckGetProcAddress za drugim. Prvi nil rezultat sproži EPdfError in pred tem pokliče UnloadLibrary, kar je namerno: delna vezava bi sicer pustila že razrešene kazalce, usmerjene v modul, ki bo kmalu sproščen, tiho porazila vsako spodnjo varovalko Assigned
Posledica je način odpovedi, ki ljudi pripelje sem. Nadgradite komponento, dostavite isti pdfium.dll, ki ste ga dostavljali dve leti, aplikacija pa se ne zažene. Napaka poimenuje izvoz za funkcijo, ki je nikoli niste poklicali. Nič, kar naredite na mestu klica, ne pomaga, ker mesto klica nikoli ne teče; napaka se je zgodila med vezavo, preden je bil odprt kakršen koli dokument
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;
Obvezno ali neobvezno: kje meja dejansko leži
Pravilo, ki ga PDFium Component uporablja, je jasno. Izvoz je obvezen, kadar njegova odsotnost onesposobi komponento za delo, za katero obstaja, in neobvezen, kadar njegova odsotnost odstrani le eno listno funkcijo. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage so obvezni, glasna odpoved pri teh pa je pravilna: pregledovalnik, ki ne more izrisati, ni degradiran pregledovalnik, temveč je pokvarjen
Vse, kar danes doseže strpen nalagalnik, je list. FPDFBookmark_GetColor je prišel po M109 in dobavi le neobvezno polje barve /C vnosa orisa, zato DLL, starejši od njega, preprosto poroča, da ni barve zaznamka. Pomočniki V8 FPDF_GetRecommendedV8Flags in FPDF_GetArrayBufferAllocatorSharedInstance ter pomočniki nizov XFA FPDF_BStr_Init, FPDF_BStr_Set in FPDF_BStr_Clear po konstrukciji manjkajo v vsaki gradnji brez V8, zato bi njihova obravnava kot obvezne naredila navaden pdfium.dll nenaložljiv. In par, ki je motiviral ta članek: FPDFAttachment_SetDescription in FPDFAttachment_GetDescription, dodana vzvodno v verigi 2026-07-13, pozneje kot datum gradnje vseh štirih binarnih datotek PDFium, ki jih projekt dostavlja pod DLLs/Win32 in DLLs/Win64. Ta zadnji primer je splošna oblika problema, ne enkraten dogodek: sloj vezave sledi vzvodnim glavam, ki se neprestano premikajo, medtem ko se DLL v vašem namestitvenem programu premika v diskretnih skokih, kadar koli ga nekdo ponovno zgradi. Vedno obstaja okno, v katerem stran Pascal ve za izvoze, ki jih nameščena binarna datoteka nima, vnaprejšnja odločitev, na katero stran obvezno/neobvezno meje pade vsak nov izvoz, pa je edina stvar, ki naredi to okno preživetja vredno
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');
Kaj naj vrata zmogljivosti naredijo na mestu klica?
Morajo biti asimetrična, in ta asimetrija je celotna zasnova. Branje, ki ne more teči, ima iskren prazen odgovor. Pisanje, ki ne more teči, nima poštenega odgovora, zato mora sprožiti izjemo. PDFium Component razdeli lastnost opisa priloge natanko po tej črti, delitev pa je tisto, kar prepreči, da bi manjkajoč izvoz postal tiha izguba podatkov. TPdf.GetAttachmentDescription preveri Assigned(FPDFAttachment_GetDescription) in izstopi s praznim WString. To ni laž: na DLL brez izvoza komponenta resnično ne more povedati, ali priloga nosi vnos /Desc, prazen opis pa se bere enako kot priloga, ki ga nikoli ni imela. Preostanek API-ja prilog, pokrit v članku o delu s prilogami PDF v Delphiju, naprej deluje nedotaknjen
TPdf.SetAttachmentDescription ubere nasprotno pot. Pokliče Check na istem preizkusu Assigned in sproži EPdfError z besedilom "Attachment descriptions are not supported by the loaded PDFium DLL". Tiha vrnitev tukaj bi bila najslabša razpoložljiva možnost: klicatelj bi nastavil opis, ne bi dobil napake, shranil datoteko in dostavil PDF, kjer opis preprosto manjka. Nihče ne opazi, dokler poznejši porabnik ne vpraša, kam je izginil
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;
Preverjanje zmogljivosti, preden ponudite funkcijo
Lovljenje izjeme je slab način za odkrivanje, kaj vaša namestitev zmore, zato PDFium Component izpostavi isti preizkus kot poimenovano funkcijo. AttachmentDescriptionFeaturesAvailable pokliče LoadLibrary in vrne, ali sta se obe polovici para razrešili. Sedi poleg V8FeaturesAvailable, XfaBStrHelpersAvailable in XfaFeaturesAvailable, ki sledijo identičnemu vzorcu za svoje lastne neobvezne skupine. Poimenovanje preizkusa je bolj pomembno, kot izgleda: logična vrednost, imenovana AttachmentDescriptionFeaturesAvailable, naslednjemu vzdrževalcu pove, da je ta funkcija pogojena na nameščeno binarno datoteko, kar goli preizkus Assigned, zakopan v postavljevalcu lastnosti, nikoli ne pove. Prav tako da plasti uporabniškega vmesnika nekaj, na kar se lahko veže, tako da je vnosno polje za urejanje opisa vnaprej onemogočeno namesto da bi sprejelo vnos in ga zavrnilo ob shranjevanju
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;
Zakaj mora pokritost vezave dokazati orodje?
Ker so številke onkraj točke, kjer jim je mogoče zaupati človeku. PDFium Component je revidiral 21 javnih glav PDFium proti vzvodni osnovi 2026-07-29 in našel 470 izvoženih funkcij C ABI. Vezava jih je že pokrivala 468. Nihče ni te vrzeli dveh našel z branjem glav; skripta jo je našla, v eni sekundi, in jo bo znova ob naslednjem vzvodnem skoku. tools/audit_pdfium_public_api.py je namerno majhen: z regularnimi izrazi ujema FPDF_EXPORT ... FPDF_CALLCONV name( čez vsako glavo v javnem imeniku, z regularnimi izrazi ujema vsak CheckGetProcAddress('Name') in TryGetProcAddress('Name') v PDFium.pas ter natisne razliki dveh množic: missing za izvoze brez vezave, stale za vezave, katerih izvoz vzvodno ne obstaja več. Izstopi z neničelnim izhodom, kadar je katera koli množica neprazna, tako da se brez dodatnih ceremonij vklopi v korak gradnje. Trenutni rezultat je 470 od 470 vezanih, manjkajočih 0, zastarelih 0
Zastarela smer si zasluži svoje mesto prav toliko kot manjkajoča. Izvoz, ki ga vzvodno odstranijo, pusti za seboj vrstico CheckGetProcAddress, ki bo trdno odpovedala vsako prihodnje nalaganje, ta vrsta gnitja pa je nevidna, dokler nekdo ne posodobi DLL. Ročni pregled najde funkcijo, na katero ste mislili; ne najde tiste, na katero niste. Upoštevajte tudi, da revizija namerno šteje oba nalagalnika kot pokritost, kar je pravilna odločitev za drsenje API-ja, in razlog, da mora biti delitev obvezno/neobvezno dokumentirana odločitev, ne stranski produkt tega, kdo je dodal vrstico
Kje neobvezna vezava preneha biti iskrena
Vredno je jasno navesti dve meji, ker je vzorec lahko preveč uporabiti. Prva je, da je nil kazalec funkcije varen le, če dobesedno vsaka pot, ki se ga dotakne, najprej preveri Assigned. V enoti, ki deklarira stotine spremenljivk funkcij cdecl, je en sam nezaščiten klic dostopna kršitev na naslovu, ki v sledi sklada nič ne pomeni. Ista disciplina, ki upravlja klicne konvencije in življenjske dobe čez mejo C, velja tudi tukaj, in je predmet članka o utrjevanju vezave PDFium proti napakam ABI in varnosti pomnilnika
Druga meja je obseg. Neobvezna vezava ni splošno dovoljenje, da naredite vse strpno. Če bi bil FPDF_RenderPageBitmap neobvezen, bi se komponenta veselo naložila in nato odpovedala na vsaki strani, s čimer bi eno jasno napako ob zagonu pretvorila v razpršitev izvajalnih napak brez očitnega vzroka. Obvezno je pravilna privzeta vrednost. Neobvezno je izjema, po kateri segate, kadar je funkcija resnično list, kadar ima odsotnost zagovorljivo degradirano obnašanje na strani branja, in kadar lahko stran pisanja zavrne s sporočilom, ki poimenuje razlog
Zasnova nalagalnika, preizkusi zmogljivosti in orodje za revizijo, opisani tukaj, so del PDFium Component za Delphi in C++Builder; stran izdelka navaja priložene binarne datoteke PDFium in celotno izpostavljeno API površino