Quando pdfium.dll rifiuta di caricarsi sulla macchina di un cliente, il componente PDFium per Delphi e Lazarus trasforma l'errore nativo criptico in una causa specifica. La routine CheckLoadLibrary rileva ERROR_BAD_EXE_FORMAT (193) as a 32/64-bit architecture mismatch, e CheckGetProcAddress segnala un'esportazione mancante dovuta a una versione di pdfium.dll più vecchia del binding. Entrambi i messaggi indicano la soluzione anziché costringerti a indovinare
Questo è importante poiché una DLL nativa può fallire in tre modi distinti, e il testo grezzo del sistema operativo li confonde tutti. L'architettura può essere errata, il file binario distribuito può essere troppo vecchio per il binding Pascal che lo chiama, oppure due thread possono entrare in conflitto per associarlo nello stesso momento. Ciascuna situazione ha una soluzione diversa, e lo scopo principale della diagnostica introdotta nelle attività sul ciclo di vita del caricamento della v2.11.0 è proprio indicarti quale problema stai riscontrando
Perché pdfium.dll non si carica restituendo un errore sul formato EXE?
L'errore Windows 193, ERROR_BAD_EXE_FORMAT, indica che il file è stato trovato e aperto ma il suo tipo di macchina PE non corrisponde al processo host. Un eseguibile a 64 bit non può caricare una pdfium.dll a 32 bit, e un eseguibile a 32 bit non può caricare una a 64 bit. Poiché il file è presente, l'istinto di cercare una DLL mancante ti porta nella direzione completamente sbagliata
Il componente PDFium intercetta questo caso in CheckLoadLibrary: quando LoadLibrary restituisce un handle nullo e GetLastError è 193, aggiunge un suggerimento sul fatto che la DLL è stata trovata ma la sua architettura non corrisponde al processo host, e punta alla build DLLs/Win32 o DLLs/Win64 corrispondente. La sottodirectory viene scelta da BuildDllSubDir, che restituisce Win64 quando IsWin64 è true e Win32 in caso contrario. Distribuendo la DLL nel percorso in cui il binding effettua la ricerca, il problema scompare
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Distribuisci pdfium.dll accanto all'eseguibile, in linea con il target di compilazione:
// <AppDir>\DLLs\Win32\pdfium.dll per un processo host a 32 bit
// <AppDir>\DLLs\Win64\pdfium.dll per un processo host a 64 bit
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // il primo utilizzo associa pdfium.dll in modo lazy
except
on E: EPdfError do
// Il messaggio indica già la causa reale: un disallineamento 32/64 bit
// (ERROR_BAD_EXE_FORMAT) o una versione di pdfium.dll precedente a questo binding
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
Cosa indica un'esportazione PDFium mancante?
La seconda classe di errori è il disallineamento della versione. PDFium introduce nuove esportazioni nel tempo, e il binding Pascal risolve ogni funzione di cui ha bisogno tramite CheckGetProcAddress durante LoadLibrary. Se un'esportazione richiesta è assente, significa che il binding è più recente della pdfium.dll presente sul disco, e la diagnosi corretta è che la DLL distribuita è obsoleta anziché corrotta. La funzione CheckGetProcAddress dichiara esattamente questo: solleva un'eccezione EPdfError segnalando che la versione di pdfium.dll distribuita è precedente a questa build del binding PDFiumPas, e indica il percorso DLLs/<subdir> in cui inserire il file binario corrispondente
Due dettagli rendono robusto questo approccio. In primo luogo, CheckGetProcAddress chiama UnloadLibrary prima del sollevamento dell'eccezione, in modo che un binding parziale non lasci mai puntatori a funzioni risolti a metà che potrebbero compromettere i tentativi successivi. In secondo luogo, il nome della DLL restituito proviene da BuildDllName, che restituisce normalmente pdfium.dll e pdfium.v8.dll quando il flag globale EnableV8Engine è impostato. Se la tua applicazione abilita il motore JavaScript V8 per moduli XFA o con script, il testo dell'errore farà riferimento alla build V8, evitando di sostituire il file errato
La DLL PDFium può essere caricata in sicurezza da un thread in background?
Il caricamento della libreria è ora sicuro da qualsiasi thread; chiamare le API PDFium da più thread invece non lo è. Si tratta di due garanzie separate, e tenerle distinte fa la differenza tra un pool di worker stabile e un crash intermittente. Il rendering in background esegue tipicamente ogni rendering di pagina tramite un worker TPdfFuture<T>, e il primo future a toccare TPdf attiva il binding lazy. Senza protezione, due worker potrebbero entrambi rilevare la libreria non caricata, eseguire entrambi la sequenza di binding, sovrascrivere l'handle del modulo e causare una perdita della prima istanza caricata
Le attività svolte per la v2.11.0 risolvono questo scenario tramite PDFiumLoadLock, una TRTLCriticalSection globale di processo che racchiude l'ingresso a LoadLibrary e UnloadLibrary. La sezione critica rende atomica la sequenza di controllo e caricamento, in modo che nessun thread possa vedere Loaded=False ed eseguire un doppio binding contemporaneamente, e nessun thread possa liberare la DLL mentre un altro è a metà del binding. Il lock viene creato nella sezione initialization dell'unità e distrutto in finalization, protetto da un flag PDFiumLoadLockReady in modo che l'accoppiamento sia sicuro anche durante l'arresto. Se hai bisogno del pattern worker-and-reply completo con cancellazione, l'articolo di accompagnamento sul rendering in background con future cancellabili lo illustra dall'inizio alla fine
uses
PDFium, FPdfAsync;
// Il metodo worker viene eseguito su un thread in background. Il primo future ad associare
// pdfium.dll è serializzato da 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; // associazione concorrente sicura
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // questo TPdf è di proprietà di un solo 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;
Cosa non protegge il load lock
Il confine qui merita di essere dichiarato chiaramente, poiché è facile interpretare in modo eccessivo la soluzione. PDFiumLoadLock serializza solo lo stato del binding globale: l'handle del modulo e le assegnazioni dei puntatori a funzione FPDF_*. L'API C di PDFium sottostante continua a non essere thread-safe, esattamente come dichiarano gli header originali, pertanto la chiamata alle funzioni FPDF_* o la condivisione di un singolo FPDF_DOCUMENT tra thread rimane una tua responsabilità di serializzazione. La struttura sicura è quella mostrata sopra: ogni worker possiede il proprio TPdf e non passa mai un documento attivo a un altro thread. Per una trattazione più approfondita di questo limite e delle regole ABI correlate, vedi la sezione sul rafforzamento del binding VCL di PDFium contro i guasti ABI e di memoria
Perché la sezione finalization e FPDF_DestroyLibrary sono importanti
Una lacuna più sottile si trovava nel percorso di chiusura. L'unità PDFium non aveva alcuna sezione finalization, il che significava che FPDF_DestroyLibrary non veniva mai eseguito all'uscita del processo; il sistema operativo recuperava la memoria della DLL saltando la pulizia interna di PDFium, ovvero il join del thread worker, la rimozione dell'isolato V8 quando EnableV8Engine è impostato, e lo smantellamento dei font e della cache. Su un normale carico di rendering si tratta di una perdita silenziosa alla chiusura, ma con il motore V8 abilitato viene perso un isolato JavaScript a ogni esecuzione, problema che emerge rapidamente in caso di automazione ripetuta
La sezione finalization ora chiama UnloadLibrary, che invoca FPDF_DestroyLibrary e libera il modulo. Questo risiede nell'ambito dell'unità per un motivo preciso: TPdf.Destroy imposta solo Active := False per chiudere il documento corrente, e deliberatamente non scarica la libreria globale, in modo che un'applicazione multi-istanza mantenga un binding PDFium condiviso attivo per l'intera durata del processo. Collocare lo smantellamento in finalization garantisce che la libreria condivisa venga rilasciata esattamente una volta, quando l'unità viene scaricata, e UnloadLibrary controlla preventivamente il flag Loaded, rendendo la chiamata un'operazione priva di effetti collaterali se PDFium non è mai stato utilizzato
Una breve checklist per la distribuzione
Tre abitudini prevengono quasi ogni errore di caricamento sul campo. Allinea l'architettura della DLL al processo host e distribuiscila in DLLs/Win32 o DLLs/Win64; mantieni pdfium.dll allineata alla versione del binding per evitare la mancanza di esportazioni richieste; e non condividere mai un TPdf o un handle di documento tra thread, anche se l'associazione stessa è ora atomica. Se compili lo stesso codice sorgente per Delphi e Lazarus, le differenze di pacchettizzazione che complicano la distribuzione cross-target sono trattate nelle note sui problemi del compilatore Delphi e FPC. La diagnostica, il blocco del caricamento e la pulizia di finalizzazione qui descritti sono inclusi in PDFium Component per Delphi e C++Builder