Når pdfium.dll nægter at indlæse på en kundemaskine, forvandler PDFium-komponenten til Delphi og Lazarus den kryptiske indfødte fejl til en specifik årsag. Rutinen CheckLoadLibrary læser ERROR_BAD_EXE_FORMAT (193) som et 32/64-bit arkitektur-mismatch, og CheckGetProcAddress rapporterer en manglende eksport som en pdfium.dll, der er ældre end bindingen. Begge meddelelser nævner løsningen i stedet for at lade dig gætte
Dette har betydning, fordi en indfødt (native) DLL kan fejle på tre forskellige måder, og operativsystemets rå fejltekst slår dem alle sammen. Arkitekturen kan være forkert, den udrullede binære fil kan være for gammel til den Pascal-binding, der kalder den, eller to tråde kan konkurrere om at binde den i samme øjeblik. Hver har sin egen løsning, og hele formålet med de diagnosticeringer, der blev tilføjet i v2.11.0-indlæsningslivscyklusarbejdet, er at fortælle dig, hvilken du rent faktisk står overfor
Hvorfor fejler pdfium.dll med en fejl om forkert EXE-format?
Windows-fejl 193, ERROR_BAD_EXE_FORMAT, betyder, at filen blev fundet og åbnet, men dens PE-maskintype stemmer ikke overens med værts-processen. En 64-bit eksekverbar fil kan ikke indlæse en 32-bit pdfium.dll, og en 32-bit eksekverbar fil kan ikke indlæse en 64-bit. Filen er til stede, så instinktet til at lede efter en manglende DLL sender dig i den stik modsatte retning
PDFium-komponenten afskærer dette tilfælde i CheckLoadLibrary: Når LoadLibrary returnerer et nul-håndtag, og GetLastError er 193, tilføjer den et tip om, at DLL'en blev fundet, men dens arkitektur ikke matcher værts-processen, og den peger på det matchende build under DLLs/Win32 eller DLLs/Win64. Underkatalogen vælges af BuildDllSubDir, som returnerer Win64, når IsWin64 er sand, og Win32 ellers. Udrul DLL'en i den mappestruktur, som bindingen rent faktisk søger i, og problemet med mismatch forsvinder
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Deploy pdfium.dll next to the executable, matched to the build target:
// <AppDir>\DLLs\Win32\pdfium.dll for a 32-bit host process
// <AppDir>\DLLs\Win64\pdfium.dll for a 64-bit host process
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // first use lazily binds pdfium.dll
except
on E: EPdfError do
// The message already names the real cause: a 32/64-bit mismatch
// (ERROR_BAD_EXE_FORMAT) or a pdfium.dll older than this binding
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
Hvad fortæller en manglende PDFium-eksport dig?
Den anden fejlklasse er versionsmæssig forskydning (version skew). PDFium udgiver nye eksporter over tid, og Pascal-bindingen finder hver funktion, den har brug for, via CheckGetProcAddress under LoadLibrary. Hvis en påkrævet eksport mangler, er bindingen nyere end den pdfium.dll, der ligger på disken, og den ærlige diagnose er, at den udrullede DLL er forældet frem for korrupt. Funktionen CheckGetProcAddress angiver præcis dette: Den udløser en EPdfError, der rapporterer, at den udrullede pdfium.dll er ældre end denne version af PDFiumPas-bindingen, og den navngiver den DLLs/<subdir>-sti, hvor en matchende binær fil hører hjemme
To detaljer gør denne sti robust. For det første kalder CheckGetProcAddress UnloadLibrary, før den fejler, så en delvis binding aldrig efterlader halvt opløste funktionspegere, som det næste forsøg kan snuble over. For det andet kommer det DLL-navn, den rapporterer, fra BuildDllName, som normalt returnerer pdfium.dll, og pdfium.v8.dll, når det globale flag EnableV8Engine er sat. Hvis din applikation aktiverer V8 JavaScript-motoren til XFA- eller scriptede formularer, peger fejlteksten på V8-buildet og ikke det almindelige, så du udskifter den rigtige fil første gang
Er det sikkert at indlæse PDFium DLL'en fra en baggrundstråd?
Indlæsning af biblioteket er nu sikkert fra enhver tråd; det er stadig ikke sikkert at kalde PDFium-API'en fra flere tråde. Dette er to separate garantier, og at holde dem adskilt er forskellen mellem en stabil pulje af arbejdstråde og et periodisk nedbrud. Baggrundsrendering kører typisk hver siderendering gennem en TPdfFuture<T>-arbejder, og den første future, der rører ved TPdf, er den, der udløser den sene binding (lazy bind). Uden beskyttelse kunne to arbejdstråde begge observere et ikke-indlæst bibliotek, begge køre bindingssekvensen, overskrive modulhåndtaget og dermed lække den første indlæsning
Arbejdet i v2.11.0 lukker det hul med PDFiumLoadLock, en proces-global TRTLCriticalSection, der omslutter indgangen til både LoadLibrary og UnloadLibrary. Den kritiske sektion gør tjek-og-indlæs-sekvensen atomar, så ikke to tråde kan se Loaded=False og foretage dobbeltbinding, og ingen tråd kan frigøre DLL'en, mens en anden er i gang med at binde. Låsen oprettes i unit'ens initialization-sektion og nedbrydes i finalization, beskyttet af et PDFiumLoadLockReady-flag, så parringen er sikker selv under nedlukning. Hvis du har brug for det fulde mønster med arbejdstråde og svar samt annullering, gennemgår ledsageartiklen om baggrundsrendering med annullationsbare futures det fra start til slut
Hvad indlæsningslåsen ikke beskytter mod
Grænsen her er værd at slå fast, da det er let at overfortolke løsningen. PDFiumLoadLock serialiserer kun den globale bindingstilstand: Modulhåndtaget og FPDF_*-funktionspeger-tildelingerne. Den underliggende PDFium C-API er stadig ikke trådsikker, præcis som de officielle headere angiver, så det er fortsat dit ansvar at serialisere kald til FPDF_*-funktioner eller deling af ét FPDF_DOCUMENT på tværs af tråde. Den sikre form er den ovenfor viste: Hver arbejder ejer sin egen TPdf og overdrager aldrig et aktivt dokument til en anden tråd. For en dybere behandling af denne grænse og ABI-reglerne omkring den, se hærdning af PDFium VCL-bindingen mod ABI- og hukommelsesfejl
Hvorfor finalization og FPDF_DestroyLibrary har betydning
Et efterladt hul lå i nedlukningsstien. PDFium-unit'en havde ingen finalization-sektion, hvilket betød, at FPDF_DestroyLibrary aldrig blev kørt ved procesafslutning; operativsystemet frigjorde DLL-hukommelsen og sprang PDFiums egen oprydning over, navnlig tilslutning af arbejds-tråde (worker-thread join), V8 isolate-bortskaffelse når EnableV8Engine er sat, samt skrifttype- og cache-nedbrydning. På en almindelig renderingsarbejdsbyrde er dette et stille læk ved nedlukning, men med V8-motoren aktiveret lækker det et JavaScript-isolate ved hver kørsel, hvilket hurtigt viser sig under gentagen automatisering
Sektionen finalization kalder nu UnloadLibrary, som aktiverer FPDF_DestroyLibrary og frigør modulet. Dette befinder sig på unit-niveau af en årsag: TPdf.Destroy sætter kun Active := False for at lukke det aktuelle dokument, og den undlader bevidst at frigøre det globale bibliotek, så en applikation med flere instanser holder én delt PDFium-binding i live under hele processens levetid. At lægge nedbrydningen i finalization betyder, at det delte bibliotek frigives præcis én gang, når unit'en frigøres, og UnloadLibrary tjekker sit Loaded-flag først, så kaldet er en harmløs no-op, hvis PDFium aldrig blev brugt
En kort tjekliste til udrulning
Tre vaner forhindrer næsten enhver indlæsningsfejl i marken. Match DLL-arkitekturen til værts-processen og lever den under DLLs/Win32 eller DLLs/Win64; hold pdfium.dll i takt med bindingsversionen, så ingen påkrævet eksport mangler; og del aldrig en TPdf eller et dokumenthåndtag på tværs af tråde, selvom selve bindingen nu er atomar. Hvis du bygger den samme kildekode til både Delphi og Lazarus, er de emballageforskelle, der kan spænde ben for udrulning på tværs af platforme, dækket i noterne om Delphi- og FPC-krydskompileringsfaldgruber. Diagnosticeringen, indlæsningslåsen og finalization-oprydningen beskrevet her leveres alle i PDFium-komponenten til Delphi og C++Builder