Quando a pdfium.dll se recusa a ser carregada na máquina de um cliente, o Componente PDFium para Delphi e Lazarus transforma o erro nativo enigmático em uma causa específica. A rotina CheckLoadLibrary lê o ERROR_BAD_EXE_FORMAT (193) como uma incompatibilidade de arquitetura de 32/64 bits, e a CheckGetProcAddress relata uma exportação ausente como uma pdfium.dll que é mais antiga do que a unit de vinculação (binding). Ambas as mensagens apontam a solução em vez de deixar você tentando adivinhar
Isso é importante porque uma DLL nativa falha de três maneiras distintas, e o texto bruto gerado pelo sistema operacional confunde todas elas. A arquitetura pode estar incorreta, o binário implantado pode ser muito antigo para a unit Pascal que o chama, ou duas threads podem tentar se vincular a ele no mesmo instante. Cada caso exige uma correção diferente, e o objetivo principal dos diagnósticos adicionados no trabalho do ciclo de vida de carregamento da v2.11.0 é informar exatamente qual cenário você está enfrentando
Por que a pdfium.dll falha ao carregar com um erro de formato de EXE inválido?
O erro 193 do Windows, ERROR_BAD_EXE_FORMAT, significa que o arquivo foi encontrado e aberto, mas seu tipo de máquina PE não coincide com o processo host. Um executável de 64 bits não pode carregar uma pdfium.dll de 32 bits, e um executável de 32 bits não pode carregar uma de 64 bits. O arquivo está presente, portanto, o instinto de procurar por uma DLL ausente o levará na direção completamente errada
O Componente PDFium intercepta esse caso em CheckLoadLibrary: quando LoadLibrary retorna um handle nulo e o GetLastError é 193, ele acrescenta uma dica de que a DLL foi encontrada, mas sua arquitetura não coincide com a do processo host, e aponta para a versão correspondente sob a pasta DLLs/Win32 ou DLLs/Win64. O subdiretório é escolhido por BuildDllSubDir, que retorna Win64 quando o IsWin64 é true e Win32 em caso contrário. Implante a DLL na estrutura de diretórios que a vinculação realmente pesquisa e a incompatibilidade desaparecerá
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
begin
// Implante a pdfium.dll ao lado do executável, correspondendo ao alvo de compilação:
// <AppDir>\DLLs\Win32\pdfium.dll para processos host de 32 bits
// <AppDir>\DLLs\Win64\pdfium.dll para processos host de 64 bits
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
Pdf.Active := True; // o primeiro uso vincula tardiamente a pdfium.dll
except
on E: EPdfError do
// A mensagem já aponta a causa real: incompatibilidade de 32/64 bits
// (ERROR_BAD_EXE_FORMAT) ou uma pdfium.dll mais antiga do que esta vinculação
raise Exception.CreateFmt('O motor PDF não iniciou: %s', [E.Message]);
end;
if Pdf.Active then
RenderFirstPage(Pdf);
finally
Pdf.Free;
end;
end;
O que uma exportação ausente do PDFium indica?
A segunda classe de falha é o descompasso de versões. O PDFium adiciona novas exportações ao longo do tempo, e a vinculação Pascal resolve cada função necessária através de CheckGetProcAddress durante o LoadLibrary. Se uma exportação obrigatória estiver ausente, a vinculação é mais recente do que a pdfium.dll em disco, e o diagnóstico honesto é que a DLL implantada está desatualizada, em vez de corrompida. A função CheckGetProcAddress afirma precisamente isso: ela gera um EPdfError relatando que a pdfium.dll implantada é mais antiga do que esta compilação da vinculação PDFiumPas, e indica o caminho DLLs/<subdir> ao qual o binário correspondente pertence
Dois detalhes tornam esse caminho robusto. Primeiro, a função CheckGetProcAddress chama UnloadLibrary antes de gerar a exceção, de modo que uma vinculação parcial nunca deixe ponteiros de função semi-resolvidos para trás, evitando falhas na próxima tentativa. Segundo, o nome da DLL que ela relata vem de BuildDllName, que retorna pdfium.dll normalmente e pdfium.v8.dll quando a flag global EnableV8Engine está ativa. Se a sua aplicação habilitar o motor 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 versão comum, garantindo que você substitua o arquivo correto na primeira tentativa
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 ainda não é. Essas são duas garantias separadas, e mantê-las separadas é a diferença entre um pool de threads de trabalho estável e um travamento intermitente. A renderização em segundo plano normalmente executa a renderização de cada página por meio de uma thread TPdfFuture<T>, e o primeiro future a tocar em TPdf é o que dispara a vinculação tardia (lazy binding). Sem proteção, duas threads de trabalho poderiam observar simultaneamente uma biblioteca não carregada, executar a sequência de vinculação, sobrescrever o handle do módulo e vazar a primeira carga
O trabalho da versão v2.11.0 elimina esse risco com o PDFiumLoadLock, uma seção crítica global do processo (TRTLCriticalSection) que envolve a entrada tanto do LoadLibrary quanto do UnloadLibrary. A seção crítica torna a sequência de verificação e carregamento atômica, de modo que nenhuma thread de trabalho concorrente possa ver Loaded=False e realizar uma vinculação dupla, e nenhuma thread possa liberar a DLL enquanto outra estiver no meio da vinculação. O lock é criado na seção de initialization da unit e destruído em finalization, monitorado por uma flag PDFiumLoadLockReady para garantir que o emparelhamento seja seguro mesmo durante o encerramento do programa. Se você precisar do padrão completo de worker e resposta com cancelamento, o artigo complementar sobre renderização em segundo plano com futures canceláveis descreve isso de ponta a ponta
uses
PDFium, FPdfAsync;
// O método worker é executado em uma thread de segundo plano. A primeira thread
// a vincular a pdfium.dll é serializada pelo PDFiumLoadLock, impedindo que um
// segundo worker concorrente realize dupla vinculação ou vaze o 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 segura e concorrente
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // este TPdf pertence exclusivamente 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 lock de carregamento não protege
O limite aqui merece ser declarado claramente, pois é fácil interpretar erroneamente a correção. O PDFiumLoadLock serializa apenas o estado global da vinculação: o handle do módulo e as atribuições de ponteiros de função FPDF_*. A API C subjacente do PDFium ainda não é thread-safe, exatamente como os cabeçalhos originais declaram, portanto, chamar funções FPDF_* ou compartilhar um FPDF_DOCUMENT entre threads continua sendo sua responsabilidade serializar. A estrutura segura é aquela mostrada acima: cada thread de trabalho possui seu próprio TPdf e nunca entrega um documento ativo a outra thread. Para um tratamento detalhado desse limite e das regras da ABI em torno dele, consulte o artigo sobre fortalecer a vinculação VCL do PDFium contra falhas de ABI e memória
Por que a finalização e FPDF_DestroyLibrary importam
Uma lacuna mais sutil residia no caminho de encerramento. A unit do PDFium não possuía seção de finalization, o que significava que o FPDF_DestroyLibrary nunca era executado na saída do processo. O sistema operacional recuperava a memória da DLL e ignorava a limpeza própria do PDFium, nomeadamente a junção da thread de trabalho, a liberação do isolate do V8 quando o EnableV8Engine estava definido, e a destruição da fonte e do cache. Em uma carga de trabalho de renderização comum, isso é um vazamento silencioso no encerramento, mas com o motor V8 habilitado, vaza um isolate JavaScript a cada execução, o que aparece rapidamente sob automação repetida
A seção de finalization agora chama UnloadLibrary, que invoca o FPDF_DestroyLibrary e libera o módulo. Isso reside no escopo 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, de modo que uma aplicação de múltiplas instâncias mantém uma única vinculação do PDFium ativa durante todo o tempo de vida do processo. Colocar a limpeza em finalization garante que a biblioteca compartilhada seja liberada exatamente uma vez, quando a unit é descarregada, e o UnloadLibrary verifica sua flag Loaded primeiro para que a chamada seja uma operação inofensiva se o PDFium nunca tiver sido usado
Uma lista curta de verificação de implantação
Três hábitos previnem quase todas as falhas de carregamento em produção. Coincida a arquitetura da DLL com a do processo host e envie-a sob a pasta DLLs/Win32 ou DLLs/Win64; mantenha a pdfium.dll alinhada com a versão da vinculação para que nenhuma exportação necessária falte; e nunca compartilhe um TPdf ou handle de documento entre threads, mesmo que o carregamento em si agora seja atômico. Se você compilar o mesmo código-fonte para Delphi e Lazarus, as diferenças de empacotamento que atrapalham a implantação entre diferentes plataformas são cobertas nas notas sobre armadilhas do compilador cruzado Delphi e FPC. Os diagnósticos, o lock de carregamento e a limpeza de finalização descritos aqui são fornecidos no Componente PDFium para Delphi e C++Builder