Când pdfium.dll refuză să se încarce pe mașina unui client, componenta PDFium pentru Delphi și Lazarus transformă eroarea nativă neclară într-o cauză specifică. Subrutina CheckLoadLibrary interpretează codul ERROR_BAD_EXE_FORMAT (193) ca o necorelare de arhitectură pe 32/64 de biți, iar CheckGetProcAddress raportează o exportare lipsă ca fiind o versiune de pdfium.dll mai veche decât legătura (binding). Ambele mesaje indică soluția directă, în loc să vă lase să ghiciți
Acest lucru este important deoarece un DLL nativ poate eșua în trei moduri distincte, iar textul brut al sistemului de operare le combină pe toate. Arhitectura poate fi greșită, fișierul binar implementat poate fi prea vechi pentru legătura Pascal care îl apelează sau două fire de execuție (threads) pot intra într-o competiție (race condition) pentru a-l asocia în același moment. Fiecare problemă are o soluție diferită, iar scopul diagnosticelor adăugate în versiunea v2.11.0 este să vă arate exact cu care dintre ele vă confruntați
De ce nu se încarcă pdfium.dll cu o eroare de format EXE nevalid?
Eroarea Windows 193, ERROR_BAD_EXE_FORMAT, înseamnă că fișierul a fost găsit și deschis, dar tipul de arhitectură PE nu se potrivește cu procesul gazdă. Un executabil pe 64 de biți nu poate încărca un pdfium.dll pe 32 de biți, iar un executabil pe 32 de biți nu poate încărca unul pe 64 de biți. Deoarece fișierul este prezent, instinctul de a căuta un DLL lipsă vă trimite într-o direcție greșită
Componenta PDFium interceptează acest caz în CheckLoadLibrary: când LoadLibrary returnează un handle nul și GetLastError este 193, aceasta adaugă un indiciu că DLL-ul a fost găsit, dar arhitectura sa nu se potrivește cu procesul gazdă și indică versiunea corespunzătoare din DLLs/Win32 sau DLLs/Win64. Subdirectorul este selectat prin BuildDllSubDir, care returnează Win64 când IsWin64 este adevărat și Win32 în caz contrar. Implementați DLL-ul în structura de directoare pe care legătura o caută de fapt, iar neconcordanța va dispărea
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;
Ce vă indică un export PDFium lipsă?
A doua clasă de erori este decalajul de versiune. PDFium adaugă exporturi noi în timp, iar legătura Pascal mapează fiecare funcție de care are nevoie prin CheckGetProcAddress în timpul LoadLibrary. Dacă un export necesar lipsește, legătura este mai nouă decât fișierul pdfium.dll de pe disc, iar diagnosticul corect este că DLL-ul implementat este învechit, nu corupt. Funcția CheckGetProcAddress specifică exact acest lucru: generează o excepție EPdfError raportând că fișierul pdfium.dll utilizat este mai vechi decât această versiune a legăturii PDFiumPas și indică calea DLLs/<subdir> unde se găsește binarul corespunzător
Două detalii fac ca această cale să fie stabilă. În primul rând, CheckGetProcAddress apelează UnloadLibrary înainte de a genera excepția, astfel încât o asociere parțială nu lasă niciodată pointeri de funcții parțial rezolvați care să afecteze încercările următoare. În al doilea rând, numele de DLL pe care îl raportează provine din BuildDllName, care returnează pdfium.dll în mod normal și pdfium.v8.dll când este activat flagul global EnableV8Engine. Dacă aplicația dvs. folosește motorul JavaScript V8 pentru formulare XFA sau scriptate, textul erorii va indica versiunea V8, nu pe cea simplă, permițându-vă să înlocuiți fișierul corect de prima dată
Este DLL-ul PDFium sigur pentru încărcare dintr-un fir de execuție secundar?
Încărcarea bibliotecii este acum sigură din orice fir de execuție; apelarea API-ului PDFium din mai multe fire de execuție în mod concurent continuă însă să nu fie sigură. Acestea sunt două garanții distincte, iar respectarea lor face diferența între un grup stabil de execuție și o blocare intermitentă a aplicației. Randarea în fundal rulează de obicei randarea fiecărei pagini printr-un proces worker TPdfFuture<T>, iar prima instanță care accesează TPdf declanșează încărcarea întârziată (lazy bind). Fără protecție, doi workeri ar putea observa simultan că biblioteca nu este încărcată, ar rula amândoi secvența de asociere, ar suprascrie handle-ul modulului și ar genera o scurgere (leak) a primei încărcări
Implementarea din v2.11.0 elimină această problemă folosind PDFiumLoadLock, o structură TRTLCriticalSection globală la nivel de proces care controlează intrarea atât în LoadLibrary, cât și în UnloadLibrary. Secțiunea critică transformă secvența de verificare și încărcare într-o operațiune atomică, astfel încât să nu existe situații în care două fire de execuție să detecteze Loaded=False și să încarce biblioteca de două ori, și niciun fir să nu poată elibera DLL-ul în timp ce un altul este în curs de încărcare. Blocajul (lock) este creat în secțiunea initialization a unității și eliminat în finalization, fiind controlat de flagul PDFiumLoadLockReady pentru o închidere sigură. Dacă aveți nevoie de un model complet worker-and-reply cu opțiune de anulare, articolul însoțitor despre randarea în fundal cu futures anulabile detaliază acest proces
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 nu protejează blocajul de încărcare
Această limită trebuie afirmată clar, deoarece este ușor să supraevaluăm corecția. PDFiumLoadLock serializează doar starea globală a legăturii: handle-ul modulului și atribuirile pointerilor de funcții FPDF_*. API-ul C de bază al PDFium nu este în continuare sigur pentru execuție concurentă pe mai multe fire (thread-safe), exact așa cum declară antetele originale, astfel încât apelarea funcțiilor FPDF_* sau partajarea unui FPDF_DOCUMENT între fire de execuție rămâne în responsabilitatea dvs. pentru serializare. Modelul sigur este cel descris mai sus: fiecare worker deține propriul obiect TPdf și nu transmite niciodată un document activ către un alt fir. Pentru o analiză detaliată a acestei limite și a regulilor ABI asociate, consultați articolul despre securizarea legăturii PDFium VCL împotriva defectelor ABI și de memorie
De ce sunt importante secțiunea finalization și FPDF_DestroyLibrary
O problemă mai subtilă exista la închiderea procesului. Unitatea PDFium nu avea o secțiune finalization, ceea ce însemna că FPDF_DestroyLibrary nu rula niciodată la ieșirea din proces; sistemul de operare elibera memoria DLL-ului, dar omitea curățarea internă a PDFium, respectiv sincronizarea firelor de execuție worker, eliberarea spațiului izolat V8 când este activat EnableV8Engine și curățarea fonturilor și a memoriei cache. Pentru o sarcină simplă de randare, aceasta reprezintă o pierdere minoră de memorie la închidere, dar cu motorul V8 activat se pierde un spațiu izolat JavaScript la fiecare rulare, lucru ce devine rapid vizibil în procesele repetate de automatizare
Secțiunea finalization apelează acum UnloadLibrary, care invocă FPDF_DestroyLibrary și eliberează modulul. Această abordare are loc la nivel de unitate dintr-un motiv precis: TPdf.Destroy setează doar Active := False pentru a închide documentul curent și nu descarcă biblioteca globală în mod deliberat, astfel încât o aplicație cu mai multe instanțe menține o single legătură PDFium partajată activă pe durata de viață a procesului. Plasarea curățării în finalization garantează că biblioteca partajată este eliberată o singură dată, la descărcarea unității, iar UnloadLibrary verifică mai întâi flagul Loaded, apelul fiind inofensiv dacă PDFium nu a fost utilizat
O scurtă listă de verificări pentru implementare
Trei reguli previn aproape orice eroare de încărcare pe teren. Potriviți arhitectura DLL-ului cu procesul gazdă și plasați-l în DLLs/Win32 sau DLLs/Win64; mențineți fișierul pdfium.dll sincronizat cu versiunea legăturii pentru a nu lipsi exporturile necesare; și nu partajați niciodată un obiect TPdf sau un handle de document între fire de execuție, deși asocierea în sine este acum atomică. Dacă compilați aceeași sursă atât pentru Delphi, cât și pentru Lazarus, diferențele de împachetare care pot pune probleme la implementarea pe mai multe platforme sunt descrise în notele despre capcanele compilatoarelor încrucișate Delphi și FPC. Modulele de diagnostic, blocajul de încărcare și curățarea din finalization sunt livrate în pachetul PDFium Component pentru Delphi și C++Builder