Když se pdfium.dll odmítne načíst na klientském počítači, komponenta PDFium pro Delphi a Lazarus promění kryptickou nativní chybu v konkrétní příčinu. Rutina `CheckLoadLibrary` vyhodnotí chybu `ERROR_BAD_EXE_FORMAT` (193) jako nesoulad 32/64bitové architektury a `CheckGetProcAddress` nahlásí chybějící export jako stav, kdy je knihovna pdfium.dll starší než vazba (binding). Obě zprávy přímo pojmenovávají řešení, místo aby vás nechaly hádat
Na tom záleží, protože nativní DLL selhává třemi odlišnými způsoby a čistý text z operačního systému je všechny spojuje dohromady. Architektura může být špatná, nasazený binární soubor může být příliš starý pro vazbu v Pascalu, která jej volá, nebo se dvě vlákna mohou pokusit jej navázat ve stejném okamžiku. Každý případ má jiné řešení a smyslem diagnostiky přidané v rámci prací na životním cyklu načítání ve verzi v2.11.0 je sdělit vám, na co se vlastně díváte
Proč načítání pdfium.dll selhává s chybou špatného formátu EXE?
Chyba Windows 193, `ERROR_BAD_EXE_FORMAT`, znamená, že soubor byl nalezen a otevřen, ale typ jeho stroje PE neodpovídá hostitelskému procesu. 64bitový spustitelný soubor nemůže načíst 32bitovou knihovnu pdfium.dll a 32bitový spustitelný soubor nemůže načíst 64bitovou. Soubor je přítomen, takže instinktivní hledání chybějící DLL vás pošle přesně opačným směrem
Komponenta PDFium zachycuje tento případ v `CheckLoadLibrary`: když `LoadLibrary` vrátí nulový handle a `GetLastError` is 193, připojí nápovědu, že DLL byla nalezena, ale její architektura neodpovídá hostitelskému procesu, a ukáže na odpovídající sestavení v `DLLs/Win32` nebo `DLLs/Win64`. Podadresář je vybrán funkcí `BuildDllSubDir`, která vrací `Win64`, pokud je `IsWin64` true, a `Win32` v opačném případě. Nasaďte DLL do stromu, který vazba skutečně prohledává, a nesoulad zmizí
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Nasadit pdfium.dll vedle spustitelného souboru v závislosti na cíli sestavení:
// <AppDir>\DLLs\Win32\pdfium.dll pro 32bitový hostitelský proces
// <AppDir>\DLLs\Win64\pdfium.dll pro 64bitový hostitelský proces
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // první použití líně naváže pdfium.dll
except
on E: EPdfError do
// Zpráva již pojmenovává skutečnou příčinu: nesoulad 32/64bitové architektury
// (ERROR_BAD_EXE_FORMAT) nebo knihovna pdfium.dll starší než tato vazba
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
Co vám sdělí chybějící export z PDFium?
Druhou třídou selhání je verze skew. PDFium postupem času přináší nové exporty a vazba v Pascalu řeší každou funkci, kterou potřebuje, pomocí `CheckGetProcAddress` během `LoadLibrary`. Pokud požadovaný export chybí, vazba je novější než pdfium.dll na disku a upřímná diagnóza zní, že nasazená DLL je zastaralá, nikoli poškozená. Funkce `CheckGetProcAddress` uvádí přesně toto: vyvolá výjimku `EPdfError` s hlášením, že nasazená pdfium.dll je starší než toto sestavení vazby PDFiumPas, a pojmenuje cestu `DLLs/
Dva detaily činí tuto cestu robustní. Za prvé, `CheckGetProcAddress` volá `UnloadLibrary` předtím, než vyvolá výjimku, takže částečné navázání nikdy neponechá napůl vyřešené ukazatele na funkce, o které by mohl zakopnout další pokus. Za druhé, název DLL, který hlásí, pochází z `BuildDllName`, což vrací standardně `pdfium.dll` a `pdfium.v8.dll`, když je nastaven globální příznak `EnableV8Engine`. Pokud vaše aplikace povoluje JavaScript engine V8 pro XFA nebo skriptované formuláře, text chyby ukazuje na sestavení s V8, nikoli na obyčejné, takže hned napoprvé nahradíte správný soubor
Je bezpečné načítat PDFium DLL z vlákna na pozadí?
Načítání knihovny je nyní bezpečné z jakéhooli vlákna; volání API PDFium z více vláken stále není. Jedná se o dvě samostatné záruky a jejich oddělování představuje rozdíl mezi stabilním fondem pracovních vláken a občasným pádem. Vykreslování na pozadí obvykle spouští vykreslení každé stránky prostřednictvím pracovního vlákna `TPdfFuture<T>` a první future, která se dotkne `TPdf`, vyvolá líné navázání. Bez ochrany by oba pracovníci mohli zaznamenat nenačtenou knihovnu, oba spustit sekvenci navázání, přepsat handle modulu a způsobit únik prvního načtení
Práce ve verzi v2.11.0 toto okno zavírají pomocí `PDFiumLoadLock`, což je procesně globální `TRTLCriticalSection`, která obaluje vstup do `LoadLibrary` i `UnloadLibrary`. Kritická sekce činí sekvenci kontroly a následného načtení atomickou, takže žádná dvě vlákna nemohou obě vidět `Loaded=False` a provést dvojité navázání, a žádné vlákno nemůže uvolnit DLL, zatímco jiné je uprostřed navazování. Zámek se vytváří v sekci `initialization` jednotky a uvolňuje se v sekci `finalization`, chráněn příznakem `PDFiumLoadLockReady`, takže párování je bezpečné i během vypínání. Pokud potřebujete kompletní vzor typu pracovník-a-odpověď se zrušením, doprovodný článek o vykreslování na pozadí se zrušitelnými futures jej popisuje od začátku do konce
uses
PDFium, FPdfAsync;
// Pracovní metoda běží na vlákně na pozadí. První future, která naváže
// pdfium.dll, je serializována pomocí PDFiumLoadLock, takže druhý souběžný
// pracovník nemůže provést dvojité navázání ani způsobit únik handle modulu.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // bezpečné souběžné navázání
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // tento 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;
Co zámek načtení nechrání
Hranici zde stojí za to uvést na rovinu, protože je snadné si opravu špatně vyložit. `PDFiumLoadLock` serializuje pouze globální stav navázání: handle modulu a přiřazení ukazatelů na funkce `FPDF_*`. Podkladové C API PDFium stále není thread-safe, přesně jak deklarují hlavičky upstreamu, takže volání funkcí `FPDF_*` nebo sdílení jednoho `FPDF_DOCUMENT` napříč vlákny zůstává na vaší odpovědnosti za serializaci. Bezpečný tvar je ten výše: každý pracovník vlastní svůj vlastní `TPdf` a nikdy nepředává živý dokument jinému vláknu. Pro hlubší rozbor této hranice a pravidel ABI kolem ní viz zabezpečení vazby PDFium VCL proti chybám ABI a paměti
Proč na sekci finalization a FPDF_DestroyLibrary záleží
Jemnější mezera byla v cestě ukončení. Jednotka PDFium neměla sekci `finalization`, což znamenalo, že se při ukončení procesu nikdy nespustilo `FPDF_DestroyLibrary`; operační systém získal zpět paměť DLL a přeskočil vlastní čištění PDFium, konkrétně připojení pracovního vlákna, uvolnění izolátu V8, když je nastaveno `EnableV8Engine`, a zrušení písma a mezipaměti. U běžné zátěže vykreslování se jedná o tichý únik při ukončení, ale s povoleným enginem V8 uniká JavaScript izolát při každém spuštění, což se při opakované automatizaci rychle projeví
Sekce `finalization` nyní volá `UnloadLibrary`, což vyvolá `FPDF_DestroyLibrary` a uvolní modul. To žije na úrovni jednotky z nějakého důvodu: `TPdf.Destroy` pouze nastavuje `Active := False` pro zavření aktuálního dokumentu a záměrně neuvolňuje globální knihovnu, takže víceinstanční aplikace udržuje jedno sdílené navázání PDFium naživu po celou dobu životnosti procesu. Umístění čištění do sekce `finalization` znamená, že sdílená knihovna se uvolní přesně jednou, když se jednotka uvolní, a `UnloadLibrary` nejprve zkontroluje svůj příznak `Loaded`, takže volání je neškodný no-op, pokud PDFium nebylo nikdy použito
Krátký kontrolní seznam pro nasazení
Tři návyky předcházejí téměř každému selhání načtení v praxi. Slaďte architekturu DLL s hostitelským procesem a expedujte ji pod `DLLs/Win32` nebo `DLLs/Win64`; udržujte pdfium.dll v souladu s verzí vazby, aby žádný požadovaný export nechyběl; a nikdy nesdílejte `TPdf` nebo handle dokumentu napříč vlákny, i když je samotné navázání nyní atomické. Pokud sestavujete stejný zdrojový kód pro Delphi i Lazarus, rozdíly v balení, které komplikují nasazení napříč cíli, jsou popsány v poznámkách o úskalích křížového překladače Delphi a FPC. Diagnostika, zámek načtení a čištění ve finalization popsané v tomto článku se dodávají v komponentě PDFium Component pro Delphi a C++Builder