När pdfium.dll vägrar att ladda på en kunds maskin förvandlar PDFium Component för Delphi och Lazarus det kryptiska inbyggda felet till en specifik orsak. Rutinen CheckLoadLibrary tolkar ERROR_BAD_EXE_FORMAT (193) som en arkitekturavvikelse mellan 32/64-bitar, och CheckGetProcAddress rapporterar en saknad export som en pdfium.dll som är äldre än bindningen. Båda meddelandena pekar på lösningen istället för att lämna dig att gissa
Det har betydelse eftersom en inbyggd DLL kan misslyckas på tre olika sätt, och operativsystemets råa feltext blandar ihop dem. Arkitekturen kan vara felaktig, den distribuerade binära filen kan vara för gammal för den Pascal-bindning som anropar den, eller så kan två trådar kapplöpa om å binda den i samma ögonblick. Var och en har en separat lösning, och hela poängen med de diagnostiska verktyg som lades till i livscykelarbetet för laddning i v2.11.0 är att berätta vilken du faktiskt har framför dig
Varför misslyckas pdfium.dll att ladda med ett ogiltigt EXE-formatfel?
Windows-fel 193, ERROR_BAD_EXE_FORMAT, betyder att filen hittades och öppnades men dess PE-maskintyp matchar inte värdprocessen. En 64-bitars körbar fil kan inte ladda en 32-bitars pdfium.dll, och en 32-bitars körbar fil kan inte ladda en 64-bitars. Filen finns där, så instinkten att leta efter en saknad DLL skickar dig i exakt fel riktning
PDFium-komponenten fångar upp detta fall i CheckLoadLibrary: när LoadLibrary returnerar en null-pekare och GetLastError är 193 lägger den till en ledtråd om att DLL-filen hittades men att dess arkitektur inte matchar värdprocessen, och den pekar på motsvarande DLLs/Win32- eller DLLs/Win64-version. Underkatalogen väljs av BuildDllSubDir, vilken returnerar Win64 när IsWin64 är sant och annars Win32. Distribuera DLL-filen under det träd som bindningen faktiskt söker i så försvinner felmatchningen
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;
Vad berättar en saknad PDFium-export för dig?
Den andra klassen av fel är versionsskillnad. PDFium levererar nya exporter över tid, och Pascal-bindningen löser varje funktion den behöver via CheckGetProcAddress under LoadLibrary. Om en obligatorisk export saknas är bindningen nyare än den pdfium.dll som finns på disken, och den ärliga diagnosen är att den distribuerade DLL-filen är föråldrad snarare än korrupt. Funktionen CheckGetProcAddress anger exakt detta: den utlöser ett EPdfError som rapporterar att den distribuerade pdfium.dll är äldre än den här versionen av PDFiumPas-bindningen, och den namnger sökvägen DLLs/<subdir> där en matchande binär fil hör hemma
Två detaljer gör den sökvägen robust. För det första anropar CheckGetProcAddress metoden UnloadLibrary innan den utlöser felet, så att en partiell bindning aldrig lämnar halv-lösta funktionspekare efter sig som nästa försök kan snubbla på. För det andra kommer DLL-namnet den rapporterar från BuildDllName, vilket returnerar pdfium.dll normalt och pdfium.v8.dll när den globala flaggan EnableV8Engine är inställd. Om ditt program aktiverar V8 JavaScript-motorn för XFA eller skriptade formulär pekar feltexten på V8-bygget, inte det vanliga, så att du byter ut rätt fil på första försöket
Är PDFium DLL säker att ladda från en bakgrundstråd?
Att ladda biblioteket är nu säkert från vilken tråd som helst; att anropa PDFium-API:et från flera trådar är det fortfarande inte. Det är två separata garantier, och att hålla dem isär är skillnaden mellan en stabil arbetarpool och en intermittent krasch. Bakgrundsrendering kör vanligtvis varje sidrendering via en TPdfFuture<T>-arbetare, och den första framtida arbetaren som rör vid TPdf är det som utlöser den lata bindningen. Utan skydd skulle två arbetare båda kunna observera ett oladdat bibliotek, båda köra bindningssekvensen, skriva över modulens pekare och läcka den första laddningen
Arbetet i v2.11.0 stänger det fönstret med PDFiumLoadLock, en process-global TRTLCriticalSection som omsluter ingången till både LoadLibrary och UnloadLibrary. Den kritiska sektionen gör kontrollera-sedan-ladda-sekvensen atomär, så att inte två trådar kan se Loaded=False samtidigt och dubbelbinda, och ingen tråd kan frigöra DLL-filen medan en annan håller på att binda den. Låset skapas i enhetens initialization-sektion och rivs ner i finalization, bevakat av en PDFiumLoadLockReady-flagga så att parningen är säker även vid avstängning. Om du behöver det fullständiga arbetar-och-svar-mönstret med avbrytning beskriver den tillhörande artikeln om bakgrundsrendering med avbrytbara terminer det från början till slut
uses
PDFium, FPdfAsync;
// Worker method runs on a background thread. The first future to bind
// pdfium.dll is serialised by PDFiumLoadLock, so a second concurrent
// worker cannot double-bind or leak the module handle.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // safe concurrent bind
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // this TPdf is owned by one thread only
finally
Pdf.Free;
end;
end;
procedure TReportForm.StartRender;
begin
TPdfFuture<TBitmap>.Run(RenderThumbnail, ThumbnailReady);
end;
procedure TReportForm.ThumbnailReady(
const AResult: TPdfFutureResult<TBitmap>);
begin
if AResult.IsSuccess then
Preview.Picture.Assign(AResult.Value);
end;
Vad laddningslåset inte skyddar
Gränsen här är värd att nämna tydligt, eftersom det är lätt att övertolka lösningen. PDFiumLoadLock serialiserar endast det globala bindningstillståndet: modulpekaren och funktionstilldelningarna för FPDF_*. Det underliggande PDFium C-API:et är fortfarande inte trådsäkert, exakt som de överordnade rubrikerna deklarerar, så att anropa FPDF_*-funktioner eller dela ett FPDF_DOCUMENT över trådar förblir ditt ansvar att serialisera. Den säkra formen är den ovan: varje arbetare äger sin egen TPdf och lämnar aldrig ett levande dokument till en annan tråd. För en djupare behandling av den gränsen och ABI-reglerna runt den, se härdning av PDFium VCL-bindningen mot ABI- och minnesfel
Varför finalisering och FPDF_DestroyLibrary spelar roll
En mer subtil lucka fanns i avstängningsvägen. PDFium-enheten saknade finalization-sektion, vilket innebar jag FPDF_DestroyLibrary aldrig kördes vid processavslut; operativsystemet återvann DLL-minnet men hoppade över PDFiums egen uppstädning, nämligen trådkopplingar, borttagning av V8-isolat när EnableV8Engine är aktiverat, samt rensning av teckensnitt och cache. Vid en vanlig renderingsarbetsbelastning är det en tyst läcka vid avstängning, men med V8-motorn aktiverad läcker den ett JavaScript-isolat vid varje körning, vilket visar sig snabbt under upprepad automatisering
Sektionen finalization anropar nu UnloadLibrary, vilket aktiverar FPDF_DestroyLibrary och frigör modulen. Detta ligger på enhetsnivå av en anledning: TPdf.Destroy ställer endast in Active := False för att stänga det aktuella dokumentet, och den avlastar medvetet inte det globala biblioteket, så att ett program med flera instanser håller en delad PDFium-bindning vid liv under hela processens livstid. Att lägga uppstädningen i finalization innebär att det delade biblioteket frigörs exakt en gång, när enheten avlastas, och UnloadLibrary kontrollerar sin Loaded-flagga först så att anropet är en ofarlig tom operation om PDFium använde aldrig
En kort checklista för distribution
Tre vanor förhindrar nästan alla laddningsfel i fält. Matcha DLL-arkitekturen till värdprocessen och skicka den under DLLs/Win32 eller DLLs/Win64; håll pdfium.dll i fas med bindningsversionen så att ingen obligatorisk export saknas; och dela aldrig en TPdf eller en dokumentpekare över trådar, även om själva bindningen nu är atomär. Om du bygger samma källkod för både Delphi och Lazarus täcker anteckningarna om Delphi- och FPC-korskompilatorfallgropar de distributionsskillnader som kan ställa till det vid korsvis distribution. Diagnostiken, laddningslåset och finaliseringsuppstädningen som beskrivs här levereras alla i PDFium Component för Delphi och C++Builder