Seu pdfium.dll carrega bem e um procedimento ainda está ausente. O PDFium Component trata isso dividindo seus bindings em duas classes: exports obrigatórios resolvidos através de CheckGetProcAddress, que abortam o carregamento por completo, e exports opcionais resolvidos através de TryGetProcAddress, que deixam um ponteiro nil e uma verificação de capacidade em seu lugar
Este não é o mesmo problema de uma DLL que não pode ser encontrada. Se sua aplicação morre com um erro de formato de EXE ruim, um arquivo ausente, ou uma incompatibilidade de arquitetura, essa história é contada em o artigo complementar sobre implantar pdfium.dll e diagnosticar falhas de carregamento. Aqui o carregador teve sucesso. O handle do módulo é válido, centenas de exports foram resolvidos, e a execução ainda termina antes de sua primeira página renderizar porque um ponto de entrada que chegou em um build mais novo do PDFium não está no binário no disco
Por que um export ausente quebra a biblioteca inteira?
Porque um binding obrigatório é um contrato rígido, e é aplicado durante uma única sequência de bind tudo-ou-nada. O PDFium Component resolve toda sua tabela de exports dentro de LoadLibrary, uma chamada CheckGetProcAddress após a outra. O primeiro resultado nil dispara EPdfError e chama UnloadLibrary antes disso, o que é deliberado: um bind parcial de outra forma deixaria ponteiros já resolvidos apontados para um módulo prestes a ser liberado, derrotando silenciosamente toda proteção Assigned a jusante
A consequência é o modo de falha que traz as pessoas até aqui. Você atualiza o componente, entrega o mesmo pdfium.dll que vem entregando há dois anos, e a aplicação não inicia. O erro nomeia um export para um recurso que você nunca chamou. Nada que você faça no local da chamada ajuda, porque o local da chamada nunca roda; a falha aconteceu durante o binding, antes de qualquer documento ser aberto
function CheckGetProcAddress(const Name: string): Pointer;
begin
Result := GetProcAddress(PDFiumLibrary, PChar(Name));
if Result = nil then
begin
// A missing required export means the deployed pdfium.dll is older
// than this build of the binding. Drop every pointer resolved so far
// so no caller can reach into the module we are about to free.
UnloadLibrary;
raise EPdfError.Create('Required PDFium export not found: ' + Name);
end;
end;
function TryGetProcAddress(const Name: string): Pointer;
begin
// Optional export. nil is a legitimate answer here; every caller is
// required to test Assigned() before dereferencing the variable.
Result := GetProcAddress(PDFiumLibrary, PChar(Name));
end;
Obrigatório ou opcional: onde a linha realmente fica
A regra que o PDFium Component aplica é direta. Um export é obrigatório quando sua ausência torna o componente incapaz de fazer o trabalho para o qual existe, e opcional quando sua ausência só remove um recurso folha. FPDF_InitLibrary, FPDF_LoadDocument, FPDF_RenderPageBitmap, FPDF_ClosePage são obrigatórios, e falhar ruidosamente nesses é correto: um visualizador que não consegue renderizar não é um visualizador degradado, é um quebrado
Tudo alcançado através do carregador tolerante hoje é uma folha. FPDFBookmark_GetColor chegou depois do M109 e só fornece o array de cor opcional /C de uma entrada de esboço, então uma DLL anterior a isso simplesmente reporta nenhuma cor de marcador. Os auxiliares V8 FPDF_GetRecommendedV8Flags e FPDF_GetArrayBufferAllocatorSharedInstance, e os auxiliares de string XFA FPDF_BStr_Init, FPDF_BStr_Set e FPDF_BStr_Clear, estão ausentes de qualquer build não-V8 por construção, então tratá-los como obrigatórios tornaria o pdfium.dll simples não carregável. E o par que motivou este artigo: FPDFAttachment_SetDescription e FPDFAttachment_GetDescription, adicionados a montante em 2026-07-13, mais tarde que a data de build dos quatro binários PDFium que o projeto fornece sob DLLs/Win32 e DLLs/Win64. Esse último caso é a forma geral do problema, não um caso único: uma camada de binding rastreia cabeçalhos a montante, que se movem continuamente, enquanto a DLL no seu instalador se move em saltos discretos sempre que alguém a reconstrói. Sempre há uma janela na qual o lado Pascal conhece exports que o binário implantado não tem, e decidir com antecedência de qual lado da linha obrigatório/opcional cada novo export cai é a única coisa que mantém essa janela sobrevivível
FPDFDoc_GetAttachmentCount := CheckGetProcAddress('FPDFDoc_GetAttachmentCount');
FPDFDoc_AddAttachment := CheckGetProcAddress('FPDFDoc_AddAttachment');
FPDFAttachment_GetName := CheckGetProcAddress('FPDFAttachment_GetName');
FPDFAttachment_GetStringValue := CheckGetProcAddress('FPDFAttachment_GetStringValue');
// Attachment descriptions were added after the bundled DLL revision.
// Keep them optional so older deployments continue to load.
FPDFAttachment_SetDescription := TryGetProcAddress('FPDFAttachment_SetDescription');
FPDFAttachment_GetDescription := TryGetProcAddress('FPDFAttachment_GetDescription');
FPDFAttachment_SetFile := CheckGetProcAddress('FPDFAttachment_SetFile');
FPDFAttachment_GetFile := CheckGetProcAddress('FPDFAttachment_GetFile');
O que um portão de capacidade deveria fazer no local da chamada?
Deveria ser assimétrico, e essa assimetria é o design inteiro. Uma leitura que não pode rodar tem uma resposta vazia honesta. Uma escrita que não pode rodar não tem nenhuma resposta honesta, então precisa disparar exceção. O PDFium Component divide a propriedade de descrição de anexo exatamente ao longo dessa linha, e a divisão é o que impede um export ausente de virar perda silenciosa de dados. TPdf.GetAttachmentDescription testa Assigned(FPDFAttachment_GetDescription) e sai com um WString vazio. Isso não é uma mentira: em uma DLL sem o export, o componente genuinamente não consegue dizer se o anexo carrega uma entrada /Desc, e uma descrição vazia lê da mesma forma que um anexo que nunca teve uma. O resto da API de anexo, coberta em o artigo sobre trabalhar com anexos de PDF em Delphi, continua funcionando intocada
TPdf.SetAttachmentDescription toma a rota oposta. Ela chama Check no mesmo teste Assigned e dispara EPdfError com o texto "Attachment descriptions are not supported by the loaded PDFium DLL". Retornar silenciosamente aqui seria a pior opção disponível: o chamador definiria uma descrição, não receberia erro, salvaria o arquivo, e entregaria um PDF onde a descrição está simplesmente ausente. Ninguém percebe até que um consumidor a jusante pergunte para onde ela foi
function TPdf.GetAttachmentDescription(Index: Integer): WString;
begin
CheckActive;
Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
Result := '';
// Read side degrades: an old DLL cannot report /Desc, and '' is
// indistinguishable from an attachment that carries no description.
if not Assigned(FPDFAttachment_GetDescription) then
Exit;
// ... two-pass buffer sizing against FPDFAttachment_GetDescription ...
end;
procedure TPdf.SetAttachmentDescription(Index: Integer; const Value: WString);
begin
CheckActive;
Check((Index >= 0) and (Index < AttachmentCount), 'Incorrect attachment index');
// Write side refuses: silently dropping the value would produce a file
// the caller believes carries a description and does not.
Check(Assigned(FPDFAttachment_SetDescription),
'Attachment descriptions are not supported by the loaded PDFium DLL');
// ... FPDFDoc_GetAttachment, then FPDFAttachment_SetDescription ...
end;
Sondando a capacidade antes de oferecer o recurso
Capturar uma exceção é uma forma pobre de descobrir o que sua implantação consegue fazer, então o PDFium Component expõe o mesmo teste como uma função nomeada. AttachmentDescriptionFeaturesAvailable chama LoadLibrary e retorna se ambas as metades do par resolveram. Ela fica ao lado de V8FeaturesAvailable, XfaBStrHelpersAvailable e XfaFeaturesAvailable, que seguem o padrão idêntico para seus próprios grupos opcionais. Nomear a sonda importa mais do que parece: um booleano chamado AttachmentDescriptionFeaturesAvailable diz ao próximo mantenedor que este recurso é condicional ao binário implantado, o que um teste Assigned nu enterrado em um setter de propriedade nunca faz. Também dá à camada de UI algo a que se vincular, então a caixa de edição de descrição é desabilitada de antemão em vez de aceitar entrada e rejeitá-la ao salvar
procedure TAttachmentFrame.SyncCapabilities;
begin
// Ask once, at form setup, instead of discovering the limit on save.
DescriptionEdit.Enabled := AttachmentDescriptionFeaturesAvailable;
if not DescriptionEdit.Enabled then
DescriptionEdit.TextHint := 'Requires a newer pdfium.dll';
end;
procedure TAttachmentFrame.SaveDescription(Pdf: TPdf; Index: Integer);
begin
if not AttachmentDescriptionFeaturesAvailable then
Exit;
Pdf.AttachmentDescription[Index] := DescriptionEdit.Text;
end;
Por que a cobertura de binding precisa ser provada por uma ferramenta?
Porque os números estão além do ponto em que um humano pode ser confiado com eles. O PDFium Component auditou 21 cabeçalhos públicos do PDFium contra uma linha de base a montante de 2026-07-29 e encontrou 470 funções exportadas de ABI C. O binding já cobria 468 delas. Ninguém localizou aquela lacuna de duas lendo cabeçalhos; um script fez, em um segundo, e vai fazer de novo no próximo salto a montante. tools/audit_pdfium_public_api.py é deliberadamente pequeno: ele faz correspondência de regex com FPDF_EXPORT ... FPDF_CALLCONV name( em todo cabeçalho no diretório público, correspondência de regex com toda CheckGetProcAddress('Name') e TryGetProcAddress('Name') em PDFium.pas, e imprime as duas diferenças de conjunto: missing para exports sem binding, stale para bindings cujo export não existe mais a montante. Ele sai com código não-zero quando qualquer um dos conjuntos é não vazio, então ele cai em um passo de build sem mais cerimônia. O resultado atual é 470 de 470 vinculados, faltando 0, obsoletos 0
A direção obsoleta ganha seu lugar tanto quanto a ausente. Um export que a montante remove deixa uma linha CheckGetProcAddress para trás que vai falhar de forma rígida todo carregamento futuro, e esse tipo de decomposição é invisível até o dia em que alguém atualiza a DLL. Revisão manual encontra a função em que você estava pensando; não encontra a que você não estava. Observe também que a auditoria deliberadamente conta ambos os carregadores como cobertura, que é a escolha certa para desvio de API e o motivo pelo qual a divisão obrigatório/opcional precisa ser uma decisão documentada em vez de um subproduto de quem quer que tenha adicionado a linha
Onde o binding opcional para de ser honesto
Duas fronteiras merecem ser ditas claramente, porque o padrão é fácil de aplicar demais. A primeira é que um ponteiro de função nil só é seguro se literalmente todo caminho que o toca testa Assigned primeiro. Em uma unit que declara centenas de variáveis de função cdecl, uma única chamada não protegida é uma violação de acesso em um endereço que não significa nada em um stack trace. A mesma disciplina que governa convenções de chamada e vidas úteis através da fronteira C se aplica aqui, e é o assunto de o artigo sobre endurecer o binding do PDFium contra falhas de ABI e segurança de memória
A segunda fronteira é escopo. Binding opcional não é uma licença geral para tornar tudo tolerante. Se FPDF_RenderPageBitmap fosse opcional, o componente carregaria feliz e depois falharia em toda página, convertendo um erro claro de inicialização em uma dispersão de erros de tempo de execução sem causa óbvia. Obrigatório é o padrão correto. Opcional é a exceção que você busca quando um recurso é genuinamente uma folha, quando a ausência tem um comportamento degradado defensável do lado da leitura, e quando o lado da escrita pode recusar com uma mensagem que nomeia o motivo
O design do carregador, as sondas de capacidade e a ferramenta de auditoria descritos aqui são fornecidos como parte do PDFium Component para Delphi e C++Builder; a página do produto lista os binários PDFium empacotados e a superfície completa de API que eles expõem