Ko se pdfium.dll noče naložiti na strankinem računalniku, komponenta PDFium Component za Delphi in Lazarus spremeni skrivnostno domačo napako v določen vzrok. Rutina CheckLoadLibrary prebere ERROR_BAD_EXE_FORMAT (193) as neujemanje 32/64-bitne arhitekture, CheckGetProcAddress pa poroča o manjkajočem izvozu (export) kot o pdfium.dll, ki je starejši od vezave. Obe sporočili poimenujeta popravek, namesto da bi vas pustili ugibati
To je pomembno, ker domači DLL lahko odpove na tri različne načine, surovo besedilo operacijskega sistema pa jih vse združuje. Arhitektura je lahko napačna, nameščena binarna datoteka je lahko prestara za vezavo Pascal, ki jo kliče, ali pa dve niti tekmujeta, katera jo bo povezala v istem trenutku. Vsaka težava ima drugačen popravek, bistvo diagnostike, dodane v delu o življenjskem ciklu nalaganja različice v2.11.0, pa je, da vam pove, katero dejansko gledate
Zakaj se pdfium.dll ne naloži z napako napačnega formata EXE?
Napaka Windows 193, ERROR_BAD_EXE_FORMAT, pomeni, da je bila datoteka najdena in odprta, vendar se njen strojni tip PE ne ujema z gostiteljskim procesom. A 64-bitna izvedljiva datoteka ne more naložiti 32-bitnega pdfium.dll, 32-bitna pa ne 64-bitnega. Datoteka je prisotna, zato vas instinkt po iskanju manjkajočega DLL-ja pošlje v popolnoma napačno smer
Komponenta PDFium prestreže ta primer v CheckLoadLibrary: ko LoadLibrary vrne ničelni kazalec in je GetLastError 193, doda namig, da je bil DLL najden, vendar se njegova arhitektura ne ujema z gostiteljskim procesom, ter pokaže na ustrezno različico DLLs/Win32 ali DLLs/Win64. Podimenik izbere BuildDllSubDir, ki vrne Win64, ko je IsWin64 True, in Win32 v nasprotnem primeru. Namestite DLL pod drevo, ki ga vezava dejansko išče, in neujemanje bo izginilo
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;
Kaj vam pove manjkajoč izvoz iz PDFium?
Drugi razred odpovedi je različica. PDFium skozi čas uvaja nove izvoze, Pascalova vezava pa razreši vsako funkcijo, ki jo potrebuje, prek CheckGetProcAddress med LoadLibrary. Če zahtevani izvoz manjka, je vezava novejša od pdfium.dll na disku, poštena diagnoza pa je, da je nameščeni DLL zastarel in ne poškodovan. Funkcija CheckGetProcAddress navaja natanko to: sproži EPdfError s poročilom, da je nameščeni pdfium.dll starejši od te različice vezave PDFiumPas, in poimenuje pot DLLs/<subdir>, kamor spada ustrezen binarni program
Dve podrobnosti naredita to pot robustno. Prvič, CheckGetProcAddress pokliče UnloadLibrary, preden sproži napako, tako da delna vezava nikoli ne pusti napol razrešenih kazalcev funkcij za naslednji poskus. Drugič, ime DLL-ja, o katerem poroča, prihaja iz BuildDllName, ki običajno vrne pdfium.dll, in pdfium.v8.dll, ko je nastavljena globalna zastavica EnableV8Engine. Če vaša aplikacija omogoča motor JavaScript V8 za obrazce XFA ali obrazce s skriptami, besedilo napake kaže na različico V8 in ne na običajno, tako da že prvič zamenjate pravo datoteko
Ali je nalaganje knjižnice PDFium DLL varno iz nitnega ozadja?
Nalaganje knjižnice je zdaj varno iz katere koli niti; klicanje API-ja PDFium iz več niti pa še vedno ni. To sta dve ločeni jamstvi in ohranjanje njiju ločenih je razlika med stabilnim bazenom delavcev in občasnim sesutjem. Upodabljanje v ozadju običajno zažene vsak izris strani prek delavca TPdfFuture<T>, prva prihodnost (future), ki se dotakne TPdf, pa je tisto, kar sproži leno vezavo (lazy bind). Brez zaščite bi lahko dva delavca opazila nenaloženo knjižnico, oba zagnala zaporedje vezave, prepisala ročaj modula in povzročila uhajanje prvega nalaganja
Delo na v2.11.0 zapre to okno s PDFiumLoadLock, procesno globalno kritično sekcijo TRTLCriticalSection, ki ovije vstop v LoadLibrary in UnloadLibrary. Kritična sekcija naredi zaporedje preverjanja in nalaganja atomsko, tako da nobeni dve niti ne moreta hkrati videti Loaded=False in se dvojno vezati, nobena nit pa ne more sprostiti DLL-ja, medtem ko je druga sredi vezave. Zaklep se ustvari v sekciji initialization enote in podre v finalization, varovan z zastavico PDFiumLoadLockReady, tako da je seznanjanje varno tudi med ugašanjem. Če potrebujete celoten vzorec delavca in odgovora s preklicem, spremni članek o upodabljanju v ozadju s preklicnimi prihodnostmi to opisuje od začetka do konca
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;
Česar zaklep nalaganja ne ščiti
Mejo je tukaj vredno odkrito zapisati, saj je popravek enostavno napačno razumeti. PDFiumLoadLock serijalizira le globalno stanje vezave: ročaj modula in dodelitve kazalcev funkcij FPDF_*. Spodnji C API PDFium še vedno ni nitno varen, natanko tako, kot izjavljajo izvorni glavi, zato klicanje funkcij FPDF_* ali deljenje enega FPDF_DOCUMENT čez niti ostaja vaša odgovornost za serijalizacijo. Varna oblika je zgornja: vsak delavec ima v lasti svoj TPdf in nikoli ne preda živega dokumenta drugi niti. Za globljo obravnavo te meje in pravil ABI okoli nje si oglejte utrjevanje vezave PDFium VCL pred napakami ABI in pomnilnika
Zakaj sta pomembna finalization in FPDF_DestroyLibrary
Precej subtilna vrzel je bila v poti ugašanja. Enota PDFium ni imela sekcije finalization, kar je pomenilo, da se FPDF_DestroyLibrary ob izhodu iz procesa nikoli ni zagnal; operacijski sistem je zahteval nazaj pomnilnik DLL-ja in preskočil lastno čiščenje PDFium-a, in sicer pridružitev delovnih niti, odstranitev izolata V8, ko je nastavljen EnableV8Engine, ter podrtje pisav in predpomnilnika. Pri običajnem upodabljanju je to le tiho uhajanje ob podrtju, z omogočenim motorjem V8 pa ob vsakem zagonu izteče JavaScript izolat, kar se hitro pokaže pod ponavljajočo se avtomatizacijo
Sekcija finalization zdaj pokliče UnloadLibrary, ki sproži FPDF_DestroyLibrary in sprosti modul. To živi na ravni dosega enote z razlogom: TPdf.Destroy nastavi le Active := False, da zapre trenutni dokument, in namerno ne razbremeni globalne knjižnice, tako da aplikacija z več instancami ohranja eno skupno vezavo PDFium živo celoten življenjski cikel procesa. Postavitev podrtja v finalization pomeni, da se skupna knjižnica sprosti natanko enkrat, ko se enota razbremeni, UnloadLibrary pa najprej preveri svojo zastavico Loaded, tako da je klic neškodljiv no-op, če PDFium ni bil nikoli uporabljen
Kratek kontrolni seznam za namestitev
Tri navade preprečijo skoraj vsak neuspeh nalaganja na terenu. Uskladite arhitekturo DLL-ja z gostiteljskim procesom in ga pošljite pod DLLs/Win32 ali DLLs/Win64; ohranite pdfium.dll usklajen z različico vezave, tako da ne manjka noben zahtevan izvoz; in nikoli ne delite TPdf ali ročaja dokumenta čez niti, čeprav je sama vezava zdaj atomska. Če gradite isti vir tako za Delphi kot za Lazarus, so razlike v pakiranju, ki ovirajo namestitev čez cilje, zajete v opombah o pastih navzkrižnih prevajalnikov Delphi in FPC. Diagnostika, zaklep nalaganja in finalizacijsko čiščenje, opisani tukaj, se dobavljajo v komponenti PDFium Component za Delphi in C++Builder