Lorsque le fichier pdfium.dll refuse de se charger sur la machine d'un client, le composant PDFium pour Delphi et Lazarus convertit l'erreur système obscure en une cause précise. La routine CheckLoadLibrary interprète l'erreur ERROR_BAD_EXE_FORMAT (193) comme une incompatibilité d'architecture 32/64 bits, et CheckGetProcAddress signale une exportation manquante liée à une version de pdfium.dll antérieure à la liaison. Ces deux messages désignent la solution au lieu de vous laisser deviner
Ceci est important car une DLL native peut échouer de trois manières distinctes, et le message brut du système d'exploitation les confond toutes. L'architecture peut être incorrecte, le binaire déployé peut être trop ancien pour la liaison Pascal qui l'appelle, ou deux threads peuvent entrer en concurrence pour le lier au même instant. Chaque cas appelle une correction différente, et l'objectif même des diagnostics ajoutés dans la version v2.11.0 pour le cycle de vie du chargement est de vous indiquer précisément la nature du problème
Pourquoi pdfium.dll échoue-t-il à se charger avec une erreur de format EXE incorrect ?
L'erreur Windows 193, ERROR_BAD_EXE_FORMAT, signifie que le fichier a été trouvé et ouvert mais que son type de machine PE ne correspond pas au processus hôte. Un exécutable 64 bits ne peut pas charger une DLL pdfium.dll 32 bits, et un exécutable 32 bits ne peut pas charger une version 64 bits. Le fichier étant présent, le premier réflexe consistant à rechercher une DLL manquante vous oriente dans la mauvaise direction
Le composant PDFium intercepte ce cas dans CheckLoadLibrary : lorsque LoadLibrary renvoie un descripteur nul et que GetLastError vaut 193, il ajoute une indication précisant que la DLL a été trouvée mais que son architecture ne correspond pas au processus hôte, et il pointe vers le binaire DLLs/Win32 ou DLLs/Win64 correspondant. Le sous-répertoire est déterminé par BuildDllSubDir, qui renvoie Win64 lorsque IsWin64 est à true et Win32 dans le cas contraire. Déployez la DLL dans l'arborescence réellement interrogée par la liaison et l'incompatibilité disparaîtra
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;
Qu'indique une exportation PDFium manquante ?
La deuxième catégorie d'échec est le décalage de version. PDFium intègre de nouvelles exportations au fil du temps, et la liaison Pascal résout chaque fonction dont elle a besoin via CheckGetProcAddress pendant l'exécution de LoadLibrary. Si une exportation requise est absente, la liaison est plus récente que le fichier pdfium.dll présent sur le disque, et le diagnostic est que la DLL déployée est obsolète et non corrompue. La fonction CheckGetProcAddress le stipule clairement : elle lève une exception EPdfError signalant que la DLL pdfium.dll déployée est plus ancienne que cette version de la liaison PDFiumPas, et indique le chemin DLLs/<subdir> où doit se trouver le binaire correspondant
Deux détails sécurisent ce chemin. Premièrement, CheckGetProcAddress appelle UnloadLibrary avant de lever l'exception, de sorte qu'une liaison partielle ne laisse jamais de pointeurs de fonction semi-résolus sur lesquels la tentative suivante pourrait buter. Deuxièmement, le nom de la DLL renvoyé provient de BuildDllName, qui renvoie pdfium.dll en temps normal et pdfium.v8.dll lorsque l'indicateur global EnableV8Engine est activé. Si votre application active le moteur JavaScript V8 pour les formulaires XFA ou scriptés, le texte de l'erreur cible la version V8 et non la version standard, ce qui vous permet de remplacer le bon fichier dès la première tentative
La DLL PDFium peut-elle être chargée en toute sécurité depuis un thread d'arrière-plan ?
Le chargement de la bibliothèque est désormais sûr depuis n'importe quel thread ; en revanche, appeler l'API PDFium depuis plusieurs threads ne l'est toujours pas. Ce sont deux garanties distinctes, et les séparer fait toute la différence entre un pool d'exécution stable et un plantage intermittent. Le rendu en arrière-plan exécute généralement chaque rendu de page via un processus de travail TPdfFuture<T>, et la première tâche à solliciter TPdf déclenche la liaison tardive (lazy bind). Sans protection, deux tâches de travail pourraient toutes deux constater que la bibliothèque n'est pas chargée, exécuter simultanément la séquence de liaison, écraser le descripteur de module et provoquer une fuite du premier chargement
Les développements de la version v2.11.0 résolvent ce problème grâce à PDFiumLoadLock, une TRTLCriticalSection globale au processus qui enveloppe l'accès à la fois à LoadLibrary et à UnloadLibrary. La section critique rend la séquence de vérification puis de chargement atomique, de sorte qu'un thread ne puisse voir Loaded=False simultanément et effectuer un double chargement, et qu'un thread ne puisse libérer la DLL pendant qu'un autre est en cours de liaison. Le verrou est créé dans la section initialization de l'unité et détruit dans la section finalization, protégé par un indicateur PDFiumLoadLockReady pour sécuriser l'opération même lors de l'arrêt de l'application. Si vous avez besoin d'un modèle complet de tâche et réponse avec annulation, l'article connexe sur le rendu en arrière-plan avec tâches annulables détaille ce sujet de bout en bout
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;
Ce que le verrou de chargement ne protège pas
La limite mérite d'être énoncée clairement, car il est facile de surévaluer la portée de ce correctif. PDFiumLoadLock ne sérialise que l'état global de la liaison : le descripteur de module et les affectations de pointeurs de fonctions FPDF_*. L'API C PDFium sous-jacente n'est toujours pas thread-safe, conformément aux déclarations des en-têtes d'origine. Il vous incombe donc de sérialiser les appels aux fonctions FPDF_* ou le partage d'un FPDF_DOCUMENT entre différents threads. La structure sûre correspond à celle présentée ci-dessus : chaque tâche possède sa propre instance TPdf et ne transmet jamais un document actif à un autre thread. Pour une analyse approfondie de cette limite et des règles ABI associées, consultez l'article sur la sécurisation de la liaison VCL de PDFium contre les failles ABI et de mémoire
Pourquoi la finalisation et FPDF_DestroyLibrary sont importantes
Une faille plus subtile résidait dans le processus d'arrêt. L'unité PDFium ne comportait pas de section finalization, ce qui signifiait que FPDF_DestroyLibrary ne s'exécutait jamais à la fermeture du processus. Le système d'exploitation récupérait la mémoire de la DLL mais ignorait le nettoyage propre de PDFium, à savoir la synchronisation des threads de travail, la libération des isolates V8 lorsque EnableV8Engine est activé, et la destruction de la police et du cache. Pour une tâche de rendu classique, cela représente une fuite mineure à l'arrêt, mais avec le moteur V8 activé, un isolate JavaScript est perdu à chaque exécution, ce qui devient rapidement problématique lors d'exécutions répétées
La section finalization appelle désormais UnloadLibrary, qui invoque FPDF_DestroyLibrary et libère le module. Cette logique est placée au niveau de l'unité pour une raison précise : TPdf.Destroy se contente de définir Active := False pour fermer le document en cours, et ne décharge délibérément pas la bibliothèque globale. Ainsi, une application multi-instance conserve une liaison PDFium partagée active pendant toute la durée de vie du processus. Placer le nettoyage dans la section finalization garantit que la bibliothèque partagée est libérée une seule fois, lors du déchargement de l'unité, et UnloadLibrary vérifie d'abord son indicateur Loaded pour que l'appel reste sans effet si PDFium n'a jamais été sollicité
Une liste de contrôle de déploiement rapide
Trois habitudes permettent d'éviter presque tous les échecs de chargement en clientèle. Adaptez l'architecture de la DLL au processus hôte et livrez-la sous DLLs/Win32 ou DLLs/Win64 ; assurez-vous que le fichier pdfium.dll correspond bien à la version de la liaison afin d'éviter toute exportation manquante ; et ne partagez jamais une instance TPdf ou un descripteur de document entre threads, même si la liaison elle-même est désormais atomique. Si vous compilez le même code source pour Delphi et Lazarus, les différences d'intégration qui compliquent le déploiement multi-cibles sont traitées dans les notes sur les pièges des compilateurs croisés Delphi et FPC. Les diagnostics, the verrou de chargement et le nettoyage de finalisation décrits ici sont tous inclus dans le composant PDFium pour Delphi et C++Builder