Artigo Técnico

Implantar a DLL do PDFium no Delphi: Corrigir falhas de carregamento

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