Jūsų pdfium.dll įsikelia sėkmingai, ir vis tiek trūksta vienos procedūros. PDFium Component tai tvarko, padalindama savo susiejimus į dvi klases: privalomus eksportus, išsprendžiamus per CheckGetProcAddress, kurie visiškai nutraukia įkėlimą, ir pasirenkamus eksportus, išsprendžiamus per TryGetProcAddress, kurie palieka nil rodyklę ir galimybės patikrą vietoj to
Tai — ne ta pati problema kaip DLL, kurios nepavyksta rasti. Jei jūsų programa žūva su bloga EXE formato klaida, trūkstamu failu ar architektūros neatitikimu, ta istorija pasakojama straipsnyje apie pdfium.dll diegimą ir įkėlimo klaidų diagnozavimą. Čia įkėlimas pavyko. Modulio rankena galiojanti, šimtai eksportų išspręsti, o paleidimas vis tiek baigiasi prieš atsivaizduojant jūsų pirmam puslapiui, nes vienas įėjimo taškas, atsiradęs naujesnėje PDFium versijoje, nėra diske esančiame dvejetainiame faile
Kodėl vienas trūkstamas eksportas sugadina visą biblioteką?
Todėl, kad privalomas susiejimas — griežtas kontraktas, ir jis vykdomas vienos visko-arba-nieko susiejimo sekos metu. PDFium Component išsprendžia visą savo eksportų lentelę LoadLibrary viduje, po vieną CheckGetProcAddress iškvietimą po kito. Pirmas nil rezultatas iškelia EPdfError ir prieš tai iškviečia UnloadLibrary, o tai sąmoninga: dalinis susiejimas kitaip paliktų jau išspręstas rodykles, nukreiptas į modulį, kuris tuoj bus paleistas, tyliai nugalintį kiekvieną tolesnę Assigned apsaugą
Pasekmė — gedimo tipas, dėl kurio žmonės čia ir atsiduria. Atnaujinate komponentą, siunčiate tą patį pdfium.dll, kurį siuntėte dvejus metus, ir programa nepasileidžia. Klaida įvardija eksportą funkcijai, kurios niekada nekvietėte. Niekas, ką darote iškvietimo vietoje, nepadeda, nes iškvietimo vieta niekada nevykdoma; gedimas įvyko susiejimo metu, prieš atveriant nė vieną 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;
Privalomas ar pasirenkamas: kur iš tikrųjų eina riba
Taisyklė, kurią taiko PDFium Component, — tiesmuka. Eksportas privalomas, kai jo nebuvimas padaro komponentą negalintį atlikti darbo, kuriam jis egzistuoja, ir pasirenkamas, kai jo nebuvimas pašalina tik vieną lapelio funkciją. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage — privalomi, ir garsus žlugimas dėl jų — teisingas: žiūryklė, negalinti atvaizduoti, — ne degraduota žiūryklė, ji sugedusi
Viskas, kas pasiekiama per tolerantišką įkėliklį šiandien, — lapelis. FPDFBookmark_GetColor atsirado po M109 ir tiekia tik pasirenkamą žymelės įrašo /C spalvos masyvą, todėl DLL, senesnis už jį, tiesiog praneša, kad jokios žymelės spalvos nėra. V8 pagalbinės funkcijos FPDF_GetRecommendedV8Flags ir FPDF_GetArrayBufferAllocatorSharedInstance, bei XFA eilučių pagalbinės funkcijos FPDF_BStr_Init, FPDF_BStr_Set ir FPDF_BStr_Clear, nesama bet kokioje ne-V8 versijoje pagal konstrukciją, todėl jų laikymas privalomomis padarytų paprastą pdfium.dll neįkeliamą. O pora, motyvavusi šį straipsnį: FPDFAttachment_SetDescription ir FPDFAttachment_GetDescription, pridėtos aukštesniame lygyje 2026-07-13, vėliau nei visų keturių PDFium dvejetainių failų, kuriuos projektas siunčia su DLLs/Win32 ir DLLs/Win64, versijos data. Tas paskutinis atvejis — bendra problemos forma, ne vienkartinis dalykas: susiejimo sluoksnis seka aukštesnio lygio antraštes, kurios nuolat juda, o DLL jūsų diegimo programoje juda diskrečiais šuoliais, kada tik kas nors ją perkuria. Visada yra langas, kuriame Pascal pusė žino apie eksportus, kurių įdiegtas dvejetainis failas neturi, o iš anksto nuspręsti, kuriai privalomo/pasirenkamo riebos pusei priklauso kiekvienas naujas eksportas, — vienintelis dalykas, laikantis tą langą išgyvenamą
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');
Ką turėtų daryti galimybės vartai iškvietimo vietoje?
Jie turėtų būti asimetriški, ir ši asimetrija — visas dizainas. Skaitymas, kuris negali veikti, turi sąžiningą tuščią atsakymą. Rašymas, kuris negali veikti, jokio sąžiningo atsakymo neturi, todėl privalo iškelti išimtį. PDFium Component padalija priedo aprašymo savybę lygiai pagal šią liniją, ir šis padalijimas — tai, kas sustabdo trūkstamą eksportą virstant tyliu duomenų praradimu. TPdf.GetAttachmentDescription patikrina Assigned(FPDFAttachment_GetDescription) ir išeina su tuščia WString. Tai — ne melas: DLL be eksporto, komponentas iš tikrųjų negali pasakyti, ar priedas neša /Desc įrašą, o tuščias aprašymas skaitomas taip pat kaip priedas, kuris niekada jo neturėjo. Likusi priedo API dalis, aprašyta straipsnyje apie darbą su PDF priedais Delphi, ir toliau veikia nepaliesta
TPdf.SetAttachmentDescription eina priešingu keliu. Ji iškviečia Check ties tuo pačiu Assigned testu ir iškelia EPdfError su tekstu „Attachment descriptions are not supported by the loaded PDFium DLL“. Tylus grįžimas čia būtų blogiausias galimas pasirinkimas: iškviečiantysis nustatytų aprašymą, negautų jokios klaidos, išsaugotų failą ir išsiųstų PDF, kuriame aprašymo tiesiog nėra. Niekas to nepastebi, kol tolesnis naudotojas neklausia, kur jis dingo
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;
Galimybės tikrinimas prieš siūlant funkciją
Išimties sugavimas — prastas būdas sužinoti, ką gali jūsų diegimas, todėl PDFium Component atveria tą patį testą kaip pavadintą funkciją. AttachmentDescriptionFeaturesAvailable iškviečia LoadLibrary ir grąžina, ar abi poros pusės buvo išspręstos. Ji stovi šalia V8FeaturesAvailable, XfaBStrHelpersAvailable ir XfaFeaturesAvailable, kurios seka identišką raštą savo pačių pasirenkamoms grupėms. Zondo pavadinimas svarbesnis, nei atrodo: loginis reikšmys, pavadintas AttachmentDescriptionFeaturesAvailable, praneša kitam prižiūrėtojui, kad ši funkcija sąlyginė nuo įdiegto dvejetainio failo, ko paprasta Assigned patikra, palaidota savybės nustatytoju, niekada nedaro. Tai taip pat suteikia sąsajos sluoksniui kažką, prie ko prisirišti, todėl aprašymo redagavimo laukelis išjungiamas iš anksto, o ne priima įvestį ir atmeta ją išsaugant
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;
Kodėl susiejimo aprėptį turi įrodyti įrankis?
Todėl, kad skaičiai jau seniai pralenkė tašką, kuriame žmogumi galima pasitikėti. PDFium Component audituotas 21 viešoji PDFium antraštė pagal 2026-07-29 aukštesnio lygio bazę, ir rado 470 eksportuotų C ABI funkcijų. Susiejimas jau apėmė 468 iš jų. Niekas nesurado to dviejų spragos, skaitant antraštes; scenarijus tai padarė per sekundę, ir jis tai padarys vėl kito aukštesnio lygio šuolio metu. tools/audit_pdfium_public_api.py sąmoningai mažas: jis regex atitinka FPDF_EXPORT ... FPDF_CALLCONV name( per kiekvieną antraštę viešame kataloge, regex atitinka kiekvieną CheckGetProcAddress('Name') ir TryGetProcAddress('Name') PDFium.pas faile ir atspausdina du aibių skirtumus: missing eksportams be susiejimo, stale susiejimams, kurių eksportas nebeegzistuoja aukštesniame lygyje. Jis grįžta ne-nuline reikšme, kai bet kuri aibė netuščia, todėl jis įsijungia į versijos žingsnį be papildomo ceremonial. Dabartinis rezultatas — 470 iš 470 susieta, trūksta 0, pasenusių 0
Pasenusi kryptis nusipelno tiek pat, kiek trūkstama. Eksportas, kurį aukštesnis lygis pašalina, palieka CheckGetProcAddress eilutę, kuri sugriaus kiekvieną būsimą įkėlimą, ir tokia rūšis puvimo — nematoma iki dienos, kai kas nors atnaujina DLL. Rankinė peržiūra randa funkciją, apie kurią galvojote; ji neranda tos, apie kurią negalvojote. Taip pat pastebėkite, kad auditas sąmoningai skaičiuoja abu įkėliklius kaip aprėptį, kas — teisingas sprendimas API driftui, ir priežastis, kodėl privalomo/pasirenkamo padalijimas turi būti dokumentuotas sprendimas, ne to, kas pridėjo eilutę, šalutinis produktas
Kur pasirenkamas susiejimas nustoja būti sąžiningas
Verta aiškiai įvardyti dvi ribas, nes raštą lengva pertaikyti. Pirma — nil funkcijos rodyklė saugi tik tada, jei doslaidžiai kiekvienas kelias, liečiantis ją, pirma patikrina Assigned. Modulyje, deklaruojančiame šimtus cdecl funkcijų kintamųjų, vienas neapsaugotas iškvietimas — prieigos pažeidimas adresu, kuris nieko nereiškia steko pėdsake. Ta pati disciplina, valdanti iškvietimo konvencijas ir gyvavimo trukmes per C ribą, taikoma ir čia, ir tai — straipsnio apie PDFium susiejimo sutvirtinimą prieš ABI ir atminties saugumo klaidas tema
Antra riba — apimtis. Pasirenkamas susiejimas — ne bendra licencija paversti viską tolerantišku. Jei FPDF_RenderPageBitmap būtų pasirenkamas, komponentas mielai įsikeltų ir tada nepavyktų kiekviename puslapyje, paverčiant vieną aiškią paleidimo klaidą į vykdymo laiko klaidų sklaidą be jokios akivaizdžios priežasties. Privalomas — teisingas numatytasis. Pasirenkamas — išimtis, kurios siekiate, kai funkcija iš tikrųjų — lapelis, kai nebuvimas turi gynybišką degradavusį elgesį skaitymo pusėje, o rašymo pusė gali atsisakyti su pranešimu, įvardijančiu priežastį
Įkėliklio dizainas, galimybių zondai ir audito įrankis, aprašyti čia, dalyvauja PDFium Component Delphi ir C++Builder platformoms; produkto puslapyje išvardyti sudėtiniai PDFium dvejetainiai failai ir visas jų atveriamas API paviršius