Tehnički članak

Neobvezni PDFium izvozi: vratnice sposobnosti u Delphiju

Vaša pdfium.dll se učitava bez problema, a jedna procedura i dalje nedostaje. PDFium Component ovo rješava dijeleći svoja povezivanja u dvije klase: obvezne izvoze razriješene kroz CheckGetProcAddress, koji posve prekidaju učitavanje, i neobvezne izvoze razriješene kroz TryGetProcAddress, koji umjesto toga ostavljaju nil pokazivač i provjeru sposobnosti

Ovo nije isti problem kao DLL koja se ne može pronaći. Ako vaša aplikacija umre s pogreškom lošeg formata EXE-a, nedostajućom datotekom, ili neslaganjem arhitekture, ta je priča ispričana u popratnom članku o postavljanju pdfium.dll i dijagnostici neuspjeha učitavanja. Ovdje je učitavač uspio. Rukovatelj modula je valjan, stotine izvoza razriješeno je, a pokretanje i dalje završava prije nego se vaša prva stranica renderira jer jedna ulazna točka koja je stigla u novijem PDFium buildu nije u binarnoj datoteci na disku

Zašto jedan nedostajući izvoz ruši cijelu knjižnicu?

Zato što je obvezno povezivanje tvrd ugovor, i provodi se tijekom jednog sve-ili-ništa slijeda povezivanja. PDFium Component razrješava cijelu svoju tablicu izvoza unutar LoadLibrary, jedan poziv CheckGetProcAddress za drugim. Prvi nil rezultat izaziva EPdfError i prije toga poziva UnloadLibrary, što je namjerno: djelomično povezivanje inače bi ostavilo već razriješene pokazivače usmjerene u modul koji se upravo oslobađa, tiho poražavajući svaku zaštitu Assigned niže u lancu

Posljedica je način neuspjeha koji ljude dovodi ovamo. Nadogradite komponentu, isporučite istu pdfium.dll koju isporučujete već dvije godine, a aplikacija se ne pokreće. Greška imenuje izvoz za značajku koju nikad niste pozvali. Ništa što učinite na mjestu poziva ne pomaže, jer se mjesto poziva nikad ne izvodi; neuspjeh se dogodio tijekom povezivanja, prije nego je bilo koji dokument otvoren

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 ili neobvezno: gdje linija zapravo sjedi

Pravilo koje PDFium Component primjenjuje je izravno. Izvoz je obvezan kad njegovo odsustvo čini komponentu nesposobnom za posao za koji postoji, a neobvezan kad njegovo odsustvo uklanja samo jednu listnu značajku. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage su obvezni, i glasan neuspjeh na njima ispravan je: preglednik koji ne može renderirati nije degradiran preglednik, nego pokvaren

Sve dostupno kroz tolerantan učitavač danas je list. FPDFBookmark_GetColor stigao je nakon M109 i dostavlja samo neobvezan niz boje /C unosa obrisa, pa DLL koja mu prethodi jednostavno prijavljuje da nema boje obrisa. V8 pomoćnici FPDF_GetRecommendedV8Flags i FPDF_GetArrayBufferAllocatorSharedInstance, i XFA pomoćnici stringova FPDF_BStr_Init, FPDF_BStr_Set i FPDF_BStr_Clear, odsutni su iz svakog ne-V8 builda po konstrukciji, pa bi njihovo tretiranje kao obveznih učinilo običnu pdfium.dll neučitivom. I par koji je motivirao ovaj članak: FPDFAttachment_SetDescription i FPDFAttachment_GetDescription, dodani uzvodno 2026-07-13, kasnije od datuma builda sva četiri PDFium binarna izdanja koje projekt isporučuje pod DLLs/Win32 i DLLs/Win64. Taj posljednji slučaj je opći oblik problema, ne jednokratan: sloj povezivanja prati uzvodna zaglavlja, koja se kontinuirano pomiču, dok se DLL u vašem instalacijskom programu pomiče u diskretnim skokovima kad god netko ponovno izgradi. Uvijek postoji prozor u kojem Pascal strana zna za izvoze koje postavljeni binarni dio nema, i unaprijed odlučivanje na kojoj strani granice obvezno/neobvezno svaki novi izvoz pada jedino je što taj prozor čini preživljivim

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');

Što bi vratnica sposobnosti trebala učiniti na mjestu poziva?

Trebala bi biti asimetrična, i ta asimetrija je cijeli dizajn. Čitanje koje se ne može izvesti ima iskren prazan odgovor. Pisanje koje se ne može izvesti nema nikakav iskren odgovor, pa mora izazvati iznimku. PDFium Component dijeli svojstvo opisa privitka točno duž te linije, a taj razdjel je ono što zaustavlja nedostajući izvoz od pretvaranja u tihi gubitak podataka. TPdf.GetAttachmentDescription testira Assigned(FPDFAttachment_GetDescription) i izlazi s praznim WString. To nije laž: na DLL-u bez izvoza, komponenta doista ne može reći nosi li privitak unos /Desc, a prazan opis čita se na isti način kao privitak koji ga nikad nije imao. Ostatak API-ja privitka, pokriven u članku o radu s PDF privicima u Delphiju, nastavlja raditi nedirnuto

TPdf.SetAttachmentDescription ide suprotnim putem. Poziva Check na istom testu Assigned i izaziva EPdfError s tekstom "Attachment descriptions are not supported by the loaded PDFium DLL". Tiho vraćanje ovdje bilo bi najgora dostupna opcija: pozivatelj bi postavio opis, ne dobio grešku, spremio datoteku, i isporučio PDF gdje je opis jednostavno odsutan. Nitko ne primijeti dok potrošač niže u lancu ne pita gdje je nestao

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;

Ispitivanje sposobnosti prije nego ponudite značajku

Hvatanje iznimke slab je način otkrivanja što vaše postavljanje može učiniti, pa PDFium Component izlaže isti test kao imenovanu funkciju. AttachmentDescriptionFeaturesAvailable poziva LoadLibrary i vraća jesu li se obje polovice para razriješile. Sjedi uz V8FeaturesAvailable, XfaBStrHelpersAvailable i XfaFeaturesAvailable, koji slijede identičan obrazac za svoje vlastite neobvezne skupine. Imenovanje sonde bitno je više nego što izgleda: bulean nazvan AttachmentDescriptionFeaturesAvailable govori sljedećem održavatelju da je ta značajka uvjetovana postavljenim binarnim dijelom, što goli test Assigned zakopan u postavljaču svojstva nikad ne čini. Također daje sloju sučelja nešto na što se vezati, tako da je okvir za uređivanje opisa unaprijed onemogućen umjesto prihvaćanja unosa i njegovog odbijanja pri spremanju

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;

Zašto pokrivenost povezivanja mora dokazati alat?

Zato što su brojke prošle točku u kojoj se čovjeku može vjerovati s njima. PDFium Component revidirao je 21 javno PDFium zaglavlje protiv uzvodne osnovice od 2026-07-29 i pronašao 470 izvezenih funkcija C ABI. Povezivanje je već pokrivalo 468 njih. Nitko nije locirao taj jaz od dva čitanjem zaglavlja; skripta jest, u sekundi, i učinit će to ponovno na sljedećem uzvodnom skoku. tools/audit_pdfium_public_api.py namjerno je malen: regex-uspoređuje FPDF_EXPORT ... FPDF_CALLCONV name( preko svakog zaglavlja u javnom direktoriju, regex-uspoređuje svaki CheckGetProcAddress('Name') i TryGetProcAddress('Name') u PDFium.pas, i ispisuje dvije razlike skupova: missing za izvoze bez povezivanja, stale za povezivanja čiji izvoz više ne postoji uzvodno. Izlazi s vrijednošću različitom od nule kad je bilo koji skup neprazan, tako da se uklapa u korak builda bez dodatne ceremonije. Trenutan rezultat je 470 od 470 povezano, 0 nedostajućih, 0 zastarjelih

Smjer zastarjelosti zaslužuje svoje mjesto jednako kao i onaj nedostajući. Izvoz koji uzvodno uklone ostavlja iza sebe redak CheckGetProcAddress koji će tvrdo srušiti svako buduće učitavanje, i ta vrsta truljenja nevidljiva je do dana kad netko ažurira DLL. Ručna recenzija nalazi funkciju o kojoj ste razmišljali; ne nalazi onu o kojoj niste. Primijetite i da revizija namjerno broji oba učitavača kao pokrivenost, što je pravi izbor za drift API-ja, i razlog zašto podjela obvezno/neobvezno mora biti dokumentirana odluka, a ne nusprodukt onoga tko je dodao redak

Gdje neobvezno povezivanje prestaje biti iskreno

Dvije granice vrijedi jasno navesti, jer je obrazac lako pretjerano primijeniti. Prva je da je nil pokazivač funkcije siguran samo ako doslovno svaki put koji ga dira prvo testira Assigned. U jedinici koja deklarira stotine varijabli funkcija cdecl, jedan nezaštićen poziv je narušavanje pristupa na adresi koja u trace-u steka ne znači ništa. Ista disciplina koja upravlja konvencijama poziva i trajanjima kroz C granicu vrijedi ovdje, i tema je članka o jačanju PDFium povezivanja protiv grešaka ABI-ja i sigurnosti memorije

Druga granica je opseg. Neobvezno povezivanje nije opća dozvola da se sve učini tolerantnim. Da je FPDF_RenderPageBitmap neobvezan, komponenta bi se rado učitala i zatim propala na svakoj stranici, pretvarajući jednu jasnu grešku pokretanja u raspršenje grešaka izvođenja bez očitog uzroka. Obvezno je ispravan zadani izbor. Neobvezno je iznimka po koju posežete kad je značajka istinski list, kad odsustvo ima obranjivo degradirano ponašanje na strani čitanja, i kad strana pisanja može odbiti s porukom koja imenuje razlog

Dizajn učitavača, sonde sposobnosti i alat revizije opisani ovdje isporučuju se kao dio PDFium Component za Delphi i C++Builder; stranica proizvoda navodi priložene PDFium binarne dijelove i punu površinu API-ja koju izlažu