Wanneer pdfium.dll weigert te laden op de machine van een klant, vertaalt de PDFium-component voor Delphi en Lazarus de cryptische systeemeigen fout naar een specifieke oorzaak. De routine CheckLoadLibrary leest ERROR_BAD_EXE_FORMAT (193) as een architectuurverschil tussen 32/64-bits, en CheckGetProcAddress rapporteert een ontbrekende export als een pdfium.dll die ouder is dan de binding. Beide berichten noemen de oplossing in plaats van u te laten gissen
Dat is belangrijk omdat een systeemeigen DLL op drie verschillende manieren kan falen, en de ruwe tekst van het besturingssysteem al deze manieren op één hoop gooit. De architectuur kan onjuist zijn, de gedistribueerde binary kan te oud zijn voor de Pascal-binding die deze aanroept, of twee threads kunnen strijden (race condition) om deze op hetzelfde moment te binden. Elk probleem heeft een andere oplossing, en het hele doel van de diagnostiek die is toegevoegd in het levenscycluswerk van v2.11.0 is om u te vertellen naar welk probleem u daadwerkelijk kijkt
Waarom faalt het laden van pdfium.dll met een bad EXE format-fout?
Windows-fout 193, ERROR_BAD_EXE_FORMAT, betekent dat het bestand is gevonden en geopend, maar dat het PE-machinetype niet overeenkomt met het hostproces. Een 64-bits executable kan geen 32-bits pdfium.dll laden, en een 32-bits executable kan geen 64-bits dll laden. Het bestand is aanwezig, dus het instinct om te zoeken naar een ontbrekende DLL stuurt u precies in de verkeerde richting
De PDFium-component onderschept dit geval in CheckLoadLibrary: wanneer LoadLibrary een null-handle retourneert en GetLastError 193 is, voegt deze een hint toe dat de DLL is gevonden maar dat de architectuur ervan niet overeenkomt met het hostproces, en verwijst deze naar de bijbehorende DLLs/Win32- of DLLs/Win64-build. De subdirectory wordt gekozen door BuildDllSubDir, die Win64 retourneert wanneer IsWin64 true is en anders Win32. Implementeer de DLL onder de mappenstructuur die de binding daadwerkelijk doorzoekt en het verschil verdwijnt
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;
Wat vertelt een ontbrekende PDFium-export u?
De tweede foutklasse is versie-afwijking. PDFium introduceert in de loop van de tijd nieuwe exports, en de Pascal-binding lost elke functie die ze nodig heeft op via CheckGetProcAddress tijdens LoadLibrary. Als een vereiste export ontbreekt, is de binding nieuwer dan the pdfium.dll op de schijf, en is de reële diagnose dat de geïmplementeerde DLL verouderd is in plaats van beschadigd. De functie CheckGetProcAddress stelt precies dat: deze werpt een EPdfError op die meldt dat de geïmplementeerde pdfium.dll ouder is dan deze build van de PDFiumPas-binding, en noemt het DLLs/<subdir>-pad waar een bijpassende binary thuishoort
Twee details maken dat pad robuust. Ten eerste roept CheckGetProcAddress UnloadLibrary aan voordat de fout wordt opgeworpen, zodat een gedeeltelijke binding nooit half opgeloste functiewijzers (pointers) achterlaat waar een volgende poging over kan struikelen. Ten tweede is de DLL-naam die wordt gerapporteerd afkomstig van BuildDllName, die normaal gesproken pdfium.dll retourneert en pdfium.v8.dll wanneer de globale vlag EnableV8Engine is ingesteld. Als uw applicatie de V8 JavaScript-engine voor XFA of gescripte formulieren inschakelt, verwijst de fouttekst naar de V8-build en niet naar de gewone build, zodat u de eerste keer direct het juiste bestand vervangt
Is de PDFium DLL veilig te laden vanuit een achtergrondthread?
Het laden van de bibliotheek is nu veilig vanaf elke thread; het aanroepen van de PDFium API vanuit meerdere threads is dat nog steeds niet. Dat zijn twee afzonderlijke garanties, en ze gescheiden houden is het verschil tussen een stabiele worker-pool en een incidentele crash. Achtergrondrendering voert elke paginarender doorgaans uit via een TPdfFuture<T>-worker, en de eerste future die TPdf raakt, is wat de lazy binding triggert. Zonder bescherming zouden twee workers allebei een niet-geladen bibliotheek kunnen waarnemen, allebei de bind-reeks uitvoeren, de module-handle overschrijven en het laden van de eerste lekkage veroorzaken
Het werk in v2.11.0 sluit dat venster met PDFiumLoadLock, een proces-globale TRTLCriticalSection die de toegang tot zowel LoadLibrary als UnloadLibrary omringt. De kritieke sectie maakt de check-then-load-reeks atomair, zodat geen twee threads allebei Loaded=False kunnen zien en dubbel kunnen binden, en geen enkele thread de DLL kan vrijgeven terwijl een andere thread aan het binden is. De vergrendeling (lock) wordt aangemaakt in de unit-initialization-sectie en afgebroken in de finalization-sectie, bewaakt door een PDFiumLoadLockReady-vlag zodat de koppeling zelfs tijdens het afsluiten veilig is. Als u het volledige worker-and-reply-patroon met annulering nodig heeft, leidt het begeleidende artikel over achtergrondrendering met annuleerbare futures u er stap voor stap doorheen
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;
Wat de laadvergrendeling niet beschermt
De grens hier moet duidelijk worden vermeld, omdat men de oplossing gemakkelijk verkeerd kan interpreteren. PDFiumLoadLock serialiseert alleen de globale binding-status: de module-handle en de toewijzingen van de FPDF_*-functiewijzers. De onderliggende PDFium C API is nog steeds niet thread-safe, precies zoals de upstream headers declareren, dus het aanroepen van FPDF_*-functies of het delen van één FPDF_DOCUMENT over threads blijft uw verantwoordelijkheid om te serialiseren. De veilige vorm is de bovenstaande: elke worker bezit zijn eigen TPdf en overhandigt nooit een live document aan een andere thread. Voor een diepere behandeling van die grens en de ABI-regels eromheen, zie de PDFium VCL-binding harden tegen ABI- en geheugenfouten
Waarom finalization en FPDF_DestroyLibrary belangrijk zijn
Een subtielere leemte zat in het afsluitpad. De PDFium-unit had geen finalization-sectie, wat betekende dat FPDF_DestroyLibrary nooit werd uitgevoerd bij het afsluiten van het proces; het besturingssysteem eiste het DLL-geheugen op en sloeg de eigen opschoning van PDFium over, namelijk de worker-thread join, de afhandeling van de V8-isolaat wanneer EnableV8Engine is ingesteld, en de afbraak van lettertypen en caches. Bij een gewone rendering-workload is dat een stil lek bij het afbreken, maar met de V8-engine ingeschakeld lekt het bij elke run een JavaScript-isolaat, wat zich snel laat zien onder herhaalde automatisering
De finalization-sectie roept nu UnloadLibrary aan, die FPDF_DestroyLibrary aanroept en de module vrijgeeft. Dit leeft om een reden op unit-niveau: TPdf.Destroy stelt alleen Active := False in om het huidige document te sluiten, en laadt de globale bibliotheek bewust niet uit, zodat een applicatie met meerdere instanties één gedeelde PDFium-binding gedurende de hele levensduur van het proces in leven houdt. Het plaatsen van de afbraak in de finalization-sectie betekent dat de gedeelde bibliotheek precies één keer wordt vrijgegeven, namelijk wanneer de unit wordt ontladen, en UnloadLibrary controleert eerst de Loaded-vlag, zodat de aanroep een onschadelijke no-op is als PDFium nooit is gebruikt
Een korte checklist voor implementatie
Drie gewoonten voorkomen bijna elke laadfout in het veld. Stem de DLL-architectuur af op het hostproces en lever deze onder DLLs/Win32 of DLLs/Win64; houd pdfium.dll synchroon met de binding-versie zodat er geen vereiste export ontbreekt; en deel nooit een TPdf of een document-handle over threads, hoewel het binden zelf nu atomair is. Als u dezelfde bron bouwt voor zowel Delphi als Lazarus, worden de verpakkingsverschillen die de distributie over verschillende doelen bemoeilijken behandeld in de notities over Delphi- en FPC-cross-compiler valkuilen. De diagnostiek, the laadvergrendeling en de opschoning bij finalisatie die hier worden beschreven, worden allemaal geleverd in de PDFium-component voor Delphi en C++Builder