Quand pdfium.dll refuse de se charger sur la machine d'un client, le composant PDFium pour Delphi et Lazarus transforme l'erreur native énigmatique en une cause précise. La routine CheckLoadLibrary lit ERROR_BAD_EXE_FORMAT (193) comme une incompatibilité d'architecture 32/64 bits, et CheckGetProcAddress signale un export manquant comme un pdfium.dll plus ancien que la liaison. Les deux messages nomment le correctif au lieu de vous laisser deviner
Cela compte parce qu'une DLL native échoue de trois façons distinctes, et que le texte brut du système d'exploitation les confond toutes. L'architecture peut être fausse, le binaire déployé peut être trop ancien pour la liaison Pascal qui l'appelle, ou deux threads peuvent courir pour l'attacher au même instant. Chacun a un correctif différent, et tout l'intérêt des diagnostics ajoutés dans le travail sur le cycle de chargement de la v2.11.0 est de vous dire lequel vous avez réellement sous les yeux
Pourquoi pdfium.dll échoue-t-il 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 un pdfium.dll 32 bits, et un exécutable 32 bits ne peut pas en charger un de 64 bits. Le fichier est présent, si bien que l'instinct de partir à la chasse à une DLL manquante vous envoie exactement dans la mauvaise direction
Le composant PDFium intercepte ce cas dans CheckLoadLibrary : quand LoadLibrary renvoie un handle 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 la version DLLs/Win32 ou DLLs/Win64 adéquate. Le sous-répertoire est choisi par BuildDllSubDir, qui renvoie Win64 quand IsWin64 est vrai et Win32 sinon. Déployez la DLL sous l'arborescence que la liaison parcourt réellement et l'incompatibilité disparaît. Un piège se situe dans le code appelant plutôt que dans le déploiement : TPdf.SetActive avale chaque échec de chargement, si bien que Active := True laisse le composant inactif sans lever d'exception, et un bloc except enveloppant l'appel ne s'exécute jamais. Pour voir le message, appelez LoadDocument avec un enregistrement TPdfLoadOptions et un TPdfLoadReport en paramètre out, qui lève la véritable EPdfError et consigne plsFailed avec le texte de l'erreur dans le rapport. Depuis PDFiumPas v3.122.1, un Active := True qui échoue remplace aussi LastLoadReport par un tel rapport d'échec, si bien que le code qui garde le chemin de la propriété peut lire la cause dans LastLoadReport.ErrorMessage au lieu de deviner
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
Report: TPdfLoadReport;
begin
// Déployez pdfium.dll à côté de l'exécutable, accordé à la cible de compilation :
// <AppDir>\DLLs\Win32\pdfium.dll pour un processus hôte 32 bits
// <AppDir>\DLLs\Win64\pdfium.dll pour un processus hôte 64 bits
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
// Active := True avalerait l'échec et resterait à False ; cette
// surcharge attache pdfium.dll paresseusement et lève la vraie exception
Pdf.LoadDocument(TPdfLoadOptions.Default(plmCompatible), Report);
except
on E: EPdfError do
// Le message nomme déjà la vraie cause : une incompatibilité 32/64 bits
// (ERROR_BAD_EXE_FORMAT) ou un pdfium.dll plus ancien que cette liaison ;
// Report.Status vaut plsFailed et Report.ErrorMessage contient le texte
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
RenderFirstPage(Pdf); // Pdf.Active est True dès que LoadDocument renvoie
finally
Pdf.Free;
end;
end;
Que vous dit un export PDFium manquant ?
La deuxième classe d'échec est le décalage de version. PDFium livre de nouveaux exports au fil du temps, et la liaison Pascal résout chaque fonction dont elle a besoin via CheckGetProcAddress pendant LoadLibrary. Si un export requis est absent, la liaison est plus récente que le pdfium.dll présent sur le disque, et le diagnostic honnête est que la DLL déployée est périmée plutôt que corrompue. La fonction CheckGetProcAddress le dit précisément : elle lève une EPdfError indiquant que le pdfium.dll déployé est plus ancien que cette version de la liaison PDFiumPas, et elle nomme le chemin DLLs/<subdir> où un binaire correspondant a sa place
Deux détails rendent ce chemin robuste. D'abord, CheckGetProcAddress appelle UnloadLibrary avant de lever, si bien qu'une liaison partielle ne laisse jamais derrière elle des pointeurs de fonction à moitié résolus sur lesquels la tentative suivante trébucherait. Ensuite, le nom de DLL qu'elle signale vient de BuildDllName, qui renvoie pdfium.dll en temps normal et pdfium.v8.dll quand l'indicateur global EnableV8Engine est posé. Si votre application active le moteur JavaScript V8 pour XFA ou pour des formulaires scriptés, le texte d'erreur pointe vers la version V8, pas vers la version simple, si bien que vous remplacez le bon fichier du premier coup
La DLL PDFium peut-elle être chargée depuis un thread de fond ?
Charger la bibliothèque est désormais sûr depuis n'importe quel thread ; appeler l'API PDFium depuis plusieurs threads ne l'est toujours pas. Ce sont deux garanties distinctes, et les garder distinctes fait la différence entre un pool de workers stable et un plantage intermittent. Le rendu en arrière-plan fait généralement passer chaque rendu de page par un worker TPdfFuture<T>, et le premier future qui touche TPdf est celui qui déclenche la liaison paresseuse. Sans protection, deux workers pourraient tous deux observer une bibliothèque non chargée, exécuter tous deux la séquence de liaison, écraser le handle de module et faire fuir le premier chargement
Le travail de la v2.11.0 ferme cette fenêtre avec PDFiumLoadLock, une TRTLCriticalSection globale au processus qui enveloppe l'entrée de LoadLibrary comme celle de UnloadLibrary. La section critique rend atomique la séquence tester-puis-charger, si bien que deux threads ne peuvent pas voir tous les deux Loaded=False et attacher deux fois, et qu'aucun thread ne peut libérer la DLL pendant qu'un autre est en pleine liaison. Le verrou est créé dans la section initialization de l'unité et démonté en finalization, gardé par un indicateur PDFiumLoadLockReady pour que l'appariement reste sûr même pendant l'arrêt. Si vous avez besoin du motif complet worker-et-réponse avec annulation, l'article compagnon sur le rendu en arrière-plan avec des futures annulables le parcourt de bout en bout
uses
PDFium, FPdfAsync;
// La méthode worker s'exécute sur un thread de fond. Le premier future qui
// attache pdfium.dll est sérialisé par PDFiumLoadLock, si bien qu'un second
// worker concurrent ne peut ni doubler la liaison ni faire fuir le handle.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // liaison concurrente sûre
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // ce TPdf appartient à un seul thread
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 frontière mérite ici d'être dite clairement, car il est facile de sur-interpréter le correctif. PDFiumLoadLock sérialise uniquement l'état de liaison global : le handle de module et les affectations de pointeurs de fonction FPDF_*. L'API C de PDFium sous-jacente n'est toujours pas sûre entre threads, exactement comme les en-têtes amont le déclarent, si bien qu'appeler des fonctions FPDF_* ou partager un même FPDF_DOCUMENT entre threads reste à votre charge de sérialiser. La forme sûre est celle ci-dessus : chaque worker possède son propre TPdf et ne confie jamais un document vivant à un autre thread. Pour un traitement plus approfondi de cette frontière et des règles ABI qui l'entourent, voyez le durcissement de la liaison VCL PDFium contre les fautes ABI et mémoire
Pourquoi finalization et FPDF_DestroyLibrary comptent
Un manque plus subtil se cachait dans le chemin d'arrêt. L'unité PDFium n'avait pas de section finalization, ce qui signifiait que FPDF_DestroyLibrary ne s'exécutait jamais à la sortie du processus ; le système d'exploitation récupérait la mémoire de la DLL et sautait le nettoyage propre à PDFium, à savoir la jonction des threads workers, la libération de l'isolat V8 quand EnableV8Engine est posé, et le démontage des polices et des caches. Sur une charge de rendu ordinaire, c'est une fuite discrète au démontage, mais avec le moteur V8 activé cela fait fuir un isolat JavaScript à chaque exécution, ce qui se voit vite sous une automatisation répétée
La section finalization appelle désormais UnloadLibrary, qui invoque FPDF_DestroyLibrary et libère le module. Cela vit à la portée de l'unité pour une raison : TPdf.Destroy pose seulement Active := False pour fermer le document courant, et il ne décharge délibérément pas la bibliothèque globale, si bien qu'une application multi-instances garde une seule liaison PDFium partagée vivante pendant toute la durée de vie du processus. Placer le démontage en finalization signifie que la bibliothèque partagée est libérée exactement une fois, quand l'unité se décharge, et UnloadLibrary vérifie d'abord son indicateur Loaded pour que l'appel soit une opération neutre et inoffensive si PDFium n'a jamais servi
Une courte liste de contrôle de déploiement
Trois habitudes préviennent presque tous les échecs de chargement sur le terrain. Accordez l'architecture de la DLL au processus hôte et livrez-la sous DLLs/Win32 ou DLLs/Win64 ; gardez pdfium.dll au pas avec la version de la liaison pour qu'aucun export requis ne manque ; et ne partagez jamais un TPdf ni un handle de document entre threads, même si la liaison elle-même est désormais atomique. Si vous compilez la même source pour Delphi et pour Lazarus, les différences d'empaquetage qui font trébucher le déploiement multicible sont couvertes dans les notes sur les pièges du compilateur croisé Delphi et FPC. Les diagnostics, le verrou de chargement et le nettoyage de finalisation décrits ici sont tous livrés dans PDFium Component pour Delphi et C++Builder