Quando a pdfium.dll se recusa a carregar no computador de um cliente, o PDFium Component para Delphi e Lazarus transforma o erro nativo críptico numa causa específica. A rotina CheckLoadLibrary interpreta o erro ERROR_BAD_EXE_FORMAT (193) como uma incompatibilidade de arquitetura de 32/64 bits, e a CheckGetProcAddress reporta a falta de uma exportação como uma pdfium.dll mais antiga do que a vinculação (binding). Ambas as mensagens indicam a correção em vez de o deixar a adivinhar
Isso é importante porque uma DLL nativa falha de três formas distintas, e o texto genérico do sistema operativo confunde-as a todas. A arquitetura pode estar incorreta, o binário implementado pode ser demasiado antigo para a vinculação em Pascal que o chama, ou duas threads podem entrar em concorrência (race condition) para o vincular no mesmo instante. Cada falha tem uma solução diferente, e o objetivo principal dos diagnósticos adicionados no trabalho do ciclo de vida de carregamento da versão 2.11.0 é indicar-lhe exatamente com qual se está a deparar
Por que a pdfium.dll falha ao carregar com um erro de formato EXE incorreto?
O erro do Windows 193, ERROR_BAD_EXE_FORMAT, significa que o ficheiro foi encontrado e aberto, mas o seu tipo de máquina PE não corresponde ao processo anfitrião (host). Um executável de 64 bits não pode carregar uma pdfium.dll de 32 bits, e un executável de 32 bits não pode carregar uma de 64 bits. O ficheiro está presente, pelo que o instinto de procurar por uma DLL em falta o envia na direção errada
O PDFium Component interceta este caso na rotina CheckLoadLibrary: quando a LoadLibrary devolve um handle nulo e o GetLastError é 193, anexa uma indicação de que a DLL foi encontrada mas a sua arquitetura não corresponde ao processo anfitrião, e aponta para a compilação correspondente em DLLs/Win32 ou DLLs/Win64. O subdiretório é escolhido pela BuildDllSubDir, que devolve Win64 quando IsWin64 é true e Win32 no caso contrário. Implemente a DLL sob a árvore de diretórios que a vinculação realmente pesquisa e a incompatibilidade desaparece
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Implemente a pdfium.dll junto ao executável, correspondente ao alvo da compilação:
// <AppDir>\DLLs\Win32\pdfium.dll para um processo anfitrião de 32 bits
// <AppDir>\DLLs\Win64\pdfium.dll para um processo anfitrião de 64 bits
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // a primeira utilização vincula a pdfium.dll de forma lazy
except
on E: EPdfError do
// A mensagem já indica a causa real: incompatibilidade 32/64 bits
// (ERROR_BAD_EXE_FORMAT) ou uma pdfium.dll mais antiga do que esta vinculação
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
O que indica a falta de uma exportação do PDFium?
A segunda classe de falha é o desvio de versão (version skew). O PDFium lança novas exportações ao longo do tempo, e a vinculação em Pascal resolve cada função de que necessita através da CheckGetProcAddress durante o processamento da LoadLibrary. Se uma exportação necessária estiver ausente, a vinculação é mais recente do que a pdfium.dll em disco, e o diagnóstico correto é que a DLL implementada está desatualizada e não corrompida. A função CheckGetProcAddress indica precisamente isso: gera um EPdfError relatando que a pdfium.dll implementada é mais antiga do que esta compilação da vinculação PDFiumPas, e indica o caminho DLLs/<subdir> onde o binário correspondente deve residir
Dois detalhes tornam esse caminho robusto. Primeiro, a CheckGetProcAddress chama a UnloadLibrary antes de gerar o erro, pelo que uma vinculação parcial nunca deixa ponteiros de função parcialmente resolvidos para a tentativa seguinte falhar. Segundo, o nome da DLL que esta reporta provém da BuildDllName, que devolve pdfium.dll normalmente e pdfium.v8.dll quando a flag global EnableV8Engine está ativa. Se a sua aplicação ativar o motor de JavaScript V8 para formulários XFA ou com scripts, o texto do erro apontará para a compilação com V8, e não para a padrão, permitindo-lhe substituir o ficheiro correto à primeira
A DLL do PDFium é segura para ser carregada a partir de uma thread em segundo plano?
Carregar a biblioteca é agora seguro a partir de qualquer thread; chamar a API do PDFium a partir de várias threads continua a não ser. Estas são duas garantias separadas, e mantê-las separadas é o que distingue uma pool de workers estável de uma falha intermitente. A renderização em segundo plano normalmente executa cada renderização de página através de um worker TPdfFuture<T>, e o primeiro future a aceder a TPdf é o que desencadeia a vinculação lazy. Sem proteção, dois workers poderiam observar uma biblioteca não carregada, executar ambos a sequência de vinculação, substituir o handle do módulo e causar a fuga (leak) do primeiro carregamento
O trabalho da versão 2.11.0 elimina essa possibilidade com a PDFiumLoadLock, uma TRTLCriticalSection global ao processo que envolve a entrada tanto da LoadLibrary como da UnloadLibrary. A secção crítica torna a sequência de verificação e carregamento atómica, para que nenhum par de threads possa visualizar Loaded=False e efetuar uma dupla vinculação, e nenhuma thread possa libertar a DLL enquanto outra estiver a meio da vinculação. O bloqueio (lock) é criado na secção de initialization da unit e eliminado na secção de finalization, protegido por uma flag PDFiumLoadLockReady para que o emparelhamento seja seguro mesmo durante o encerramento. Se necessitar do padrão completo de worker e resposta com cancelamento, o artigo complementar sobre renderização em segundo plano com futures canceláveis descreve-o de ponta a ponta
uses
PDFium, FPdfAsync;
// O método worker executa numa thread em segundo plano. O primeiro future a vincular
// a pdfium.dll é serializado pela PDFiumLoadLock, pelo que um segundo worker
// concorrente não pode efetuar uma dupla vinculação ou causar fuga do handle do módulo.
function TReportForm.RenderThumbnail(
const AToken: IPdfCancellationToken): TBitmap;
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'report.pdf';
Pdf.Active := True; // vinculação concorrente segura
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // esta TPdf pertence apenas a uma 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;
O que o bloqueio de carregamento não protege
O limite aqui deve ser indicado claramente, pois é fácil interpretar a correção de forma excessiva. A PDFiumLoadLock serializa apenas o estado de vinculação global: o handle do módulo e as atribuições de ponteiros de função FPDF_*. A API C subjacente do PDFium continua a não ser thread-safe, exatamente como declaram os cabeçalhos originais, pelo que chamar as funções FPDF_* ou partilhar um FPDF_DOCUMENT entre threads continua a ser da sua responsabilidade serializar. A estrutura segura é a apresentada acima: cada worker é proprietário da sua própria TPdf e nunca entrega um documento ativo a outra thread. Para uma análise mais profunda desse limite e das regras de ABI em torno do mesmo, consulte fortalecer a vinculação PDFium VCL contra falhas de ABI e de memória
Por que a secção finalization e a FPDF_DestroyLibrary são importantes
Existia uma lacuna mais subtil no processo de encerramento. A unit PDFium não tinha secção de finalization, o que significava que a FPDF_DestroyLibrary nunca executava na saída do processo; o sistema operativo reclamava a memória da DLL e ignorava a limpeza própria do PDFium, nomeadamente a junção (join) das threads de workers, a eliminação do isolate do V8 quando a EnableV8Engine está ativa, e a destruição de fontes e caches. Numa carga de trabalho de renderização simples, trata-se de uma fuga silenciosa no encerramento, mas com o motor V8 ativado, ocorre a fuga de um isolate de JavaScript em cada execução, o que se torna evidente rapidamente sob automação repetida
A secção de finalization chama agora a UnloadLibrary, que invoca a FPDF_DestroyLibrary e liberta o módulo. Isto reside no âmbito da unit por uma razão: o método TPdf.Destroy apenas define Active := False para fechar o documento atual, e deliberadamente não descarrega a biblioteca global, para que uma aplicação com múltiplas instâncias mantenha uma vinculação PDFium partilhada ativa durante todo o tempo de vida do processo. Colocar a destruição na finalization significa que a biblioteca partilhada é libertada exatamente uma vez, quando a unit é descarregada, e a UnloadLibrary verifica primeiro a sua flag Loaded para que a chamada seja uma operação nula (no-op) inofensiva se o PDFium nunca tiver sido utilizado
Uma lista curta de verificação para a implementação
Três hábitos evitam quase todas as falhas de carregamento em produção. Adeque a arquitetura da DLL ao processo anfitrião e envie-a sob DLLs/Win32 ou DLLs/Win64; mantenha a pdfium.dll em sincronia com a versão da vinculação para que nenhuma exportação necessária fique em falta; e nunca partilhe uma TPdf ou um handle de documento entre threads, embora a vinculação em si seja agora atómica. Se compilar o mesmo código-fonte para Delphi e Lazarus, as diferenças de empacotamento que complicam a implementação cruzada de alvos são abordadas nas notas sobre armadilhas do compilador cruzado Delphi e FPC. Os diagnósticos, the bloqueio de carregamento e a limpeza de finalização aqui descritos são fornecidos com o PDFium Component para Delphi e C++Builder