Keď sa súbor pdfium.dll odmietne načítať na počítači zákazníka, komponent PDFium Component pre Delphi a Lazarus premení kryptickú natívnu chybu na konkrétnu príčinu. Rutina CheckLoadLibrary číta chybu ERROR_BAD_EXE_FORMAT (193) ako nesúlad 32/64-bitovej architektúry a CheckGetProcAddress hlási chýbajúci export ako pdfium.dll, ktorá je staršia ako väzba (binding). Obe správy pomenúvajú riešenie namiesto toho, aby vás nechali hádať
To je dôležité, pretože natívna DLL zlyháva tromi odlišnými spôsobmi a surový text operačného systému ich všetky spája. Architektúra môže byť nesprávna, nasadený binárny súbor môže byť príliš starý pre väzbu v Pascale, ktorá ho volá, alebo sa dve vlákna môžu predbiehať v jeho naviazaní v rovnakom okamihu. Každý prípad má iné riešenie a celým zmyslom diagnostiky pridanej v rámci životného cyklu načítania vo verzii v2.11.0 je povedať vám, na ktorý z nich sa v skutočnosti pozeráte
Prečo pdfium.dll zlyháva pri načítaní s chybou nesprávneho formátu EXE?
Chyba systému Windows 193, ERROR_BAD_EXE_FORMAT, znamená, že súbor bol nájdený a otvorený, ale jeho typ stroja PE nezodpovedá hostiteľskému procesu. 64-bitový spustiteľný súbor nemôže načítať 32-bitovú knižnicu pdfium.dll a 32-bitový nemôže načítať 64-bitovú. Súbor je prítomný, takže inštinkt hľadať chýbajúcu DLL vás pošle presne nesprávnym smerom
Komponent PDFium zachytáva tento prípad v metóde CheckLoadLibrary: keď LoadLibrary vráti nulový ukazovateľ a GetLastError je 193, pripojí radu, že DLL sa našla, ale jej architektúra nezodpovedá hostiteľskému procesu, a ukáže na zodpovedajúci build v DLLs/Win32 alebo DLLs/Win64. Podadresár vyberá funkcia BuildDllSubDir, ktorá vracia Win64, ak je IsWin64 true, a Win32 v opačnom prípade. Nasaďte DLL do stromovej štruktúry, ktorú väzba skutočne prehľadáva, a nesúlad zmizne
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;
Čo vám napovie chýbajúci export PDFium?
Druhým typom zlyhania je nesúlad verzií. PDFium časom vydáva nové exportované funkcie a väzba Pascalu rieši každú funkciu, ktorú potrebuje, cez CheckGetProcAddress počas LoadLibrary. Ak požadovaný export chýba, väzba je novšia ako pdfium.dll na disku a úprimnou diagnózou je, že nasadená DLL je zastaraná a nie poškodená. Funkcia CheckGetProcAddress uvádza presne toto: vyvolá chybu EPdfError hlásiacu, že nasadená pdfium.dll je staršia ako táto verzia väzby PDFiumPas, a pomenúva cestu DLLs/<subdir>, kam patrí zodpovedajúci binárny súbor
Dva detaily robia túto cestu robustnou. Po prvé, CheckGetProcAddress pred vyvolaním chyby zavolá UnloadLibrary, takže čiastočné naviazanie nikdy nenechá napoly vyriešené smerníky na funkcie, o ktoré by sa mohol potknúť ďalší pokus. Po druhé, názov DLL, ktorý hlási, pochádza z funkcie BuildDllName, ktorá za normálnych okolností vracia pdfium.dll a pdfium.v8.dll, keď je nastavený globálny príznak EnableV8Engine. Ak vaša aplikácia povoľuje JavaScript engine V8 pre XFA alebo skriptované formuláre, text chyby ukazuje na V8 build a nie na ten základný, takže hneď na prvýkrát nahradíte správny súbor
Je bezpečné načítať PDFium DLL z vlákna na pozadí?
Načítanie knižnice je teraz bezpečné z akéhokoľvek vlákna; volanie API PDFium z viacerých vlákien však stále nie je. Ide o dve samostatné záruky a ich udržiavanie oddelene je rozdielom medzi stabilným poolom vlákien a občasným pádom aplikácie. Vykresľovanie na pozadí zvyčajne spúšťa vykreslenie každej stránky prostredníctvom workera TPdfFuture<T> a prvý future, ktorý sa dotkne TPdf, spustí lenivé naviazanie (lazy bind). Bez ochrany by obaja workeri mohli spozorovať nenačítanú knižnicu, obaja spustiť naväzovaciu sekvenciu, prepísať handle modulu a spôsobiť únik prvého načítania
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;
Čo zámok načítania nechráni
Túto hranicu stojí za to uviesť jasne, pretože je ľahké prehnať interpretáciu tejto opravy. PDFiumLoadLock serializuje iba globálny stav naviazania: handle modulu a priradenia smerníkov na funkcie FPDF_*. Podkladové C API PDFium stále nie je bezpečné pre vlákna, presne tak, ako to deklarujú hlavičkové súbory, takže volanie funkcií FPDF_* alebo zdieľanie jedného FPDF_DOCUMENT naprieč vláknami zostáva na vašej zodpovednosti, aby ste ho serializovali. Bezpečná forma je uvedená vyššie: každý worker vlastní svoj vlastný TPdf and never hands a live document to another thread. Podrobnejšie sa tejto hranici a pravidlám ABI okolo nej venuje článok o zabezpečení väzby PDFium VCL proti chybám ABI a pamäte
Prečo na finalizácii a FPDF_DestroyLibrary záleží
V ceste ukončenia chýbala dôležitá časť. Jednotka PDFium nemala sekciu finalization, čo znamenalo, že FPDF_DestroyLibrary sa pri ukončení procesu nikdy nespustilo. Operačný systém prevzal pamäť DLL a preskočil vlastné čistenie PDFium, konkrétne pripojenie worker vlákna, likvidáciu izolátu V8, keď je nastavené EnableV8Engine, a uvoľnenie písiem a cache. Pri bežnej záťaži vykresľovania ide o tichý únik pri ukončení, ale s povoleným engine V8 uniká izolát JavaScriptu pri každom spustení, čo sa rýchlo prejaví pri opakovanej automatizácii
Sekcia finalization teraz volá UnloadLibrary, čo vyvolá FPDF_DestroyLibrary a uvoľní modul. Toto žije na úrovni jednotky z nejakého dôvodu: TPdf.Destroy iba nastavuje Active := False na zatvorenie aktuálneho dokumentu a zámerne neuvoľňuje globálnu knižnicu, takže aplikácia s viacerými inštanciami udržiava jedno zdieľané naviazanie PDFium živé počas celého životného cyklu procesu. Umiestnenie uvoľnenia do sekcie finalization znamená, že zdieľaná knižnica sa uvoľní presne raz, keď sa jednotka odnačíta, a UnloadLibrary najprv skontroluje svoj príznak Loaded, takže volanie je neškodné a bez operácie (no-op), ak sa PDFium nikdy nepoužilo
Krátky zoznam úloh na nasadenie
Tri návyky predchádzajú takmer každému zlyhaniu načítania v praxi. Prispôsobte architektúru DLL hostiteľskému procesu a dodajte ju pod DLLs/Win32 alebo DLLs/Win64; udržiavajte pdfium.dll v súlade s verziou väzby, aby nechýbal žiadny požadovaný export; a nikdy nezdieľajte TPdf ani handle dokumentu naprieč vláknami, aj keď samotné naviazanie je teraz atomické. Ak zostavujete rovnaký zdrojový kód pre Delphi aj Lazarus, rozdiely v balení, ktoré komplikujú nasadenie na rôzne cieľové platformy, sú popísané v poznámkach o nástrahách cross-kompilátora Delphi a FPC. Diagnostika, zámok načítania a čistenie vo finalizácii popísané v tomto článku sa dodávajú v komponente PDFium Component pre Delphi a C++Builder