Quando a pdfium.dll se recusa a carregar na máquina de um cliente, o PDFium Component para Delphi e Lazarus transforma o erro nativo enigmático em uma causa específica. A rotina CheckLoadLibrary lê ERROR_BAD_EXE_FORMAT (193) como uma incompatibilidade de arquitetura entre 32 e 64 bits, e CheckGetProcAddress reporta um export ausente como uma pdfium.dll mais antiga que a ligação. As duas mensagens nomeiam a correção em vez de deixar você adivinhando
Isso importa porque uma DLL nativa falha de três maneiras distintas, e o texto bruto do sistema operacional mistura todas elas. A arquitetura pode estar errada, o binário implantado pode ser velho demais para a ligação Pascal que o chama, ou duas threads podem correr para vinculá-lo no mesmo instante. Cada caso tem uma correção diferente, e todo o sentido dos diagnósticos acrescentados no trabalho de ciclo de vida de carga da v2.11.0 é dizer qual deles você está de fato vendo
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 o tipo de máquina do PE dele não bate com o processo hospedeiro. Um executável de 64 bits não consegue carregar uma pdfium.dll de 32 bits, e um executável de 32 bits não consegue carregar uma de 64 bits. O arquivo está presente, então o instinto de caçar uma DLL ausente manda você exatamente na direção errada
O PDFium Component intercepta esse caso em CheckLoadLibrary: quando LoadLibrary devolve um handle nulo e GetLastError é 193, ele acrescenta uma dica de que a DLL foi encontrada mas a arquitetura dela não bate com o processo hospedeiro, e aponta para a compilação correspondente em DLLs/Win32 ou DLLs/Win64. O subdiretório é escolhido por BuildDllSubDir, que devolve Win64 quando IsWin64 é verdadeiro e Win32 caso contrário. Implante a DLL na árvore que a ligação de fato procura e a incompatibilidade desaparece. Uma armadilha fica no código chamador, não na implantação: TPdf.SetActive engole toda falha de carga, então Active := True deixa o componente inativo sem levantar exceção, e um bloco except em volta dele nunca roda. Para ver a mensagem, chame LoadDocument com um record TPdfLoadOptions e um TPdfLoadReport de saída (out), que levanta o EPdfError real e registra plsFailed com o texto do erro no report. Desde o PDFiumPas v3.122.1, um Active := True que falha também substitui LastLoadReport por um failure report assim, então código que mantém o caminho da propriedade pode ler a causa em LastLoadReport.ErrorMessage em vez de adivinhar
uses
SysUtils, PDFium;
procedure OpenDocument(const AFileName: string);
var
Pdf: TPdf;
Report: TPdfLoadReport;
begin
// Implante a pdfium.dll ao lado do executável, casada com o alvo de compilação:
// <AppDir>\DLLs\Win32\pdfium.dll para um processo hospedeiro de 32 bits
// <AppDir>\DLLs\Win64\pdfium.dll para um processo hospedeiro de 64 bits
Pdf := TPdf.Create(nil);
try
Pdf.FileName := AFileName;
try
// Active := True engoliria a falha e ficaria False; esta
// sobrecarga vincula a pdfium.dll sob demanda e levanta a exceção real
Pdf.LoadDocument(TPdfLoadOptions.Default(plmCompatible), Report);
except
on E: EPdfError do
// A mensagem já nomeia a causa real: uma incompatibilidade de 32 e 64 bits
// (ERROR_BAD_EXE_FORMAT) ou uma pdfium.dll mais antiga que esta ligação;
// Report.Status é plsFailed e Report.ErrorMessage guarda o texto
raise Exception.CreateFmt('PDF engine did not start: %s', [E.Message]);
end;
RenderFirstPage(Pdf); // Pdf.Active é True assim que LoadDocument retorna
finally
Pdf.Free;
end;
end;
O que um export ausente do PDFium diz a você?
A segunda classe de falha é defasagem de versão. O PDFium publica novos exports com o tempo, e a ligação Pascal resolve cada função de que precisa por CheckGetProcAddress durante o LoadLibrary. Se um export exigido está ausente, a ligação é mais nova que a pdfium.dll em disco, e o diagnóstico honesto é que a DLL implantada está desatualizada, não corrompida. A função CheckGetProcAddress diz exatamente isso: ela levanta um EPdfError relatando que a pdfium.dll implantada é mais antiga que esta compilação da ligação PDFiumPas, e nomeia o caminho DLLs/<subdir> em que um binário compatível deve ficar
Dois detalhes tornam esse caminho robusto. Primeiro, CheckGetProcAddress chama UnloadLibrary antes de levantar a exceção, então uma vinculação parcial nunca deixa ponteiros de função meio resolvidos para a próxima tentativa tropeçar. Segundo, o nome de DLL que ela reporta vem de BuildDllName, que devolve pdfium.dll normalmente e pdfium.v8.dll quando a flag global EnableV8Engine está ligada. Se a sua aplicação habilita o motor JavaScript V8 para XFA ou formulários com script, o texto do erro aponta para a compilação com V8, não para a simples, então você troca o arquivo certo já na primeira vez
É seguro carregar a DLL do PDFium de uma thread em segundo plano?
Carregar a biblioteca agora é seguro a partir de qualquer thread; chamar a API do PDFium de várias threads continua não sendo. São duas garantias separadas, e mantê-las separadas é a diferença entre um pool de trabalho estável e uma falha intermitente. A renderização em segundo plano em geral roda cada renderização de página por um worker TPdfFuture<T>, e o primeiro future que toca o TPdf é o que dispara a vinculação sob demanda. Sem proteção, dois workers poderiam ambos observar a biblioteca não carregada, ambos executar a sequência de vinculação, sobrescrever o handle do módulo e vazar a primeira carga
O trabalho da v2.11.0 fecha essa janela com o PDFiumLoadLock, uma TRTLCriticalSection global do processo que envolve a entrada tanto de LoadLibrary quanto de UnloadLibrary. A seção crítica torna atômica a sequência de checar e então carregar, então duas threads não podem ambas ver Loaded=False e vincular duas vezes, e nenhuma thread pode liberar a DLL enquanto outra está no meio da vinculação. O lock é criado na seção initialization da unit e desmontado no finalization, guardado por uma flag PDFiumLoadLockReady para que o par seja seguro mesmo durante o encerramento. Se você precisa do padrão completo de worker e resposta com cancelamento, o artigo complementar sobre renderização em segundo plano com futures canceláveis o percorre de ponta a ponta
uses
PDFium, FPdfAsync;
// O método worker roda em uma thread em segundo plano. O primeiro future a
// vincular a pdfium.dll é serializado pelo PDFiumLoadLock, então um segundo
// worker concorrente não consegue vincular em dobro nem vazar 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 concorrente segura
AToken.ThrowIfCancelled;
Result := Pdf.Thumbnail; // este TPdf pertence a uma única 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 carga não protege
Vale enunciar o limite aqui com clareza, porque é fácil ler demais na correção. O PDFiumLoadLock serializa apenas o estado global de vinculação: o handle do módulo e as atribuições dos ponteiros de função FPDF_*. A API C do PDFium por baixo continua não sendo segura para threads, exatamente como os cabeçalhos originais declaram, então chamar funções FPDF_* ou compartilhar um mesmo FPDF_DOCUMENT entre threads continua sendo responsabilidade sua serializar. O formato seguro é o de cima: cada worker é dono do próprio TPdf e nunca entrega um documento vivo a outra thread. Para um tratamento mais profundo desse limite e das regras de ABI em volta dele, veja endurecer a ligação VCL do PDFium contra falhas de ABI e de memória
Por que finalization e FPDF_DestroyLibrary importam
Uma lacuna mais sutil ficava no caminho de encerramento. A unit PDFium não tinha seção finalization, o que significava que FPDF_DestroyLibrary nunca rodava na saída do processo; o sistema operacional recuperava a memória da DLL e pulava a limpeza própria do PDFium, a saber, o join das threads de trabalho, o descarte do isolate do V8 quando EnableV8Engine está ligado e a desmontagem de fontes e caches. Em uma carga de trabalho de renderização simples isso é um vazamento discreto no encerramento, mas com o motor V8 habilitado ele vaza um isolate de JavaScript a cada execução, o que aparece rápido sob automação repetida
A seção finalization agora chama UnloadLibrary, que invoca FPDF_DestroyLibrary e libera o módulo. Isso vive no escopo da unit por um motivo: 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 com várias instâncias mantém uma única ligação compartilhada do PDFium viva por todo o tempo de vida do processo. Colocar a desmontagem no finalization significa que a biblioteca compartilhada é liberada exatamente uma vez, quando a unit descarrega, e UnloadLibrary confere antes a flag Loaded, de modo que a chamada é inofensiva se o PDFium nunca foi usado
Uma lista de verificação curta de implantação
Três hábitos evitam quase toda falha de carga em campo. Case a arquitetura da DLL com o processo hospedeiro e distribua-a sob DLLs/Win32 ou DLLs/Win64; mantenha a pdfium.dll em passo com a versão da ligação, para que nenhum export exigido falte; e nunca compartilhe um TPdf ou um handle de documento entre threads, mesmo que a vinculação em si agora seja atômica. Se você compila o mesmo fonte para Delphi e Lazarus, as diferenças de empacotamento que atrapalham a implantação entre alvos estão tratadas nas notas sobre armadilhas de compilação cruzada entre Delphi e FPC. Os diagnósticos, o lock de carga e a limpeza no finalization descritos aqui acompanham todos o PDFium Component para Delphi e C++Builder