O componente PDFium encontra a sua biblioteca nativa através de uma cadeia de procura fixa e ordenada em vez de o deixar ao carregador do sistema operativo, porque uma árvore de implantação explícita é uma árvore de implantação que se pode depurar. No Windows essa cadeia procura um subdiretório Win32 ou Win64 que o instalador já distribui. Nos outros alvos constrói o nome do subdiretório a partir das macros de alvo do Free Pascal, como <cpu>-<os>, pelo que a árvore de implantação se lê exatamente como a árvore de unidades compiladas. Essa última decisão introduziu um bug que vale o artigo inteiro, porque a causa era uma maiúscula e o sintoma era silêncio
A cadeia, pela ordem
Quatro lugares, tentados em sequência, e depois o carregador da plataforma como último recurso. Primeiro a disposição preferida, um diretório DLLs ao lado do executável contendo um subdiretório por alvo. Segundo uma disposição alternativa com o subdiretório de alvo diretamente ao lado do executável. Terceiro a disposição legada plana, a biblioteca ao lado do executável sem subdiretório nenhum. Quarto, só no Windows, o diretório de sistema, que precisa de cuidado porque um processo de 32 bits tem de procurar em SysWOW64 e um de 64 bits em System32, e no Windows de 32 bits o primeiro não existe, pelo que a procura tem de recuar. Só depois de tudo isso se pede ao carregador que procure por conta própria
Fora do Windows não há deliberadamente nenhum passo de diretório de sistema. O caminho de procura do próprio carregador da plataforma, dirigido pela configuração do linker de runtime e pelas variáveis de ambiente de caminho de bibliotecas, já cobre esse terreno, e duplicá-lo em Pascal significaria reimplementar regras que variam por distribuição. Diagnosticar falhas na cadeia Windows está coberto separadamente em implantação da DLL PDFium e diagnóstico de falhas de carregamento
De onde vem o nome do subdiretório
No Windows é Win32 ou Win64, decidido pela largura de bits do processo em execução e não pela do sistema operativo, porque é isso que determina que binário pode ser carregado. Em todo o resto o nome é construído a partir das macros de alvo do compilador para que uma máquina que compile para duas arquiteturas produza duas árvores claramente separadas, e para que a pasta que guarda a biblioteca nativa fique ao lado da pasta que guarda as unidades compiladas com o mesmo nome
function BuildDllSubDir(UseV8: Boolean): string;
begin
{$IFDEF MSWINDOWS}
if IsWin64 then
Result := 'Win64'
else
Result := 'Win32';
{$ELSE}
// As macros do compilador escrevem o SO com maiúscula inicial
// ("Linux", "Darwin") enquanto o diretório de saída de unidades do
// pacote não o faz, pelo que os dois só coincidem depois de dobrar
// a caixa. Num sistema de ficheiros sensível a maiúsculas essa
// diferença é a procura inteira
Result := LowerCase({$I %FPCTARGETCPU%} + '-' + {$I %FPCTARGETOS%});
{$ENDIF}
end;
Porque é que uma maiúscula partiu a cadeia inteira
A macro do compilador escreve o sistema operativo alvo com maiúscula inicial: Win64, Linux, Darwin. O pacote Lazarus escreve a sua saída de unidades num diretório nomeado a partir da sua própria variável de alvo, que é minúscula: win64, linux, darwin. Duas grafias da mesma coisa, e nenhuma forma de dar por isso no Windows, onde o sistema de ficheiros as não distingue
No Linux são dois diretórios diferentes. Uma implantação que ponha o objeto partilhado em DLLs/x86_64-linux é invisível para um carregador que procure DLLs/x86_64-Linux, pelo que todos os quatro passos explícitos da cadeia falham e o código cai para deixar o carregador da plataforma procurar. Às vezes isso funciona, se a biblioteca por acaso estiver instalada em todo o sistema, e às vezes não, e de qualquer forma a árvore de implantação cuidadosamente arranjada não contribui com nada. A falha não tem mensagem de erro porque nada falhou: cada passo reportou corretamente que o ficheiro não estava onde procurou
O programa de sondagem, compilado e corrido
Esta classe de bug não se encontra lendo, e também não se encontra compilando. A técnica habitual para verificar um ramo de plataforma que nunca compila na máquina de desenvolvimento é copiar a unidade para um diretório temporário, renomeá-la, substituir a condicional de plataforma por um símbolo nunca definido, e compilar a cópia; se compilar, a cláusula uses e as assinaturas de chamada nesse caminho são pelo menos autoconsistentes. Isso funciona bem para uma unidade autónoma
Aqui não funciona. A unidade principal de vinculação é muito grande e puxa a LCL, pelo que não pode simplesmente ser copiada e compilada com o símbolo Windows desligado. Em vez disso, o punhado de funções que a alteração tocou foi transcrito verbatim para um pequeno programa autónomo, e esse programa foi corrido. Imprimiu x86_64-Win64, e a discrepância era visível numa linha de saída. Compilar o mesmo programa não lhe teria dito nada, porque a string é perfeitamente válida; só o seu valor está errado
program ProbeSubDir;
{$MODE DELPHI}
uses
SysUtils;
begin
// Imprimir, não afirmar. O ponto é ver o valor a que uma macro
// realmente se expande nesta toolchain
Writeln('raw: ', {$I %FPCTARGETCPU%}, '-', {$I %FPCTARGETOS%});
Writeln('folded: ', LowerCase({$I %FPCTARGETCPU%} + '-' +
{$I %FPCTARGETOS%}));
end.
A lição geral: quando uma alteração multiplataforma concerne o valor de alguma coisa em vez do seu tipo, verificação só por compilação não é verificação. Imprima-o. O conjunto mais amplo de diferenças entre compiladores entre Delphi e Free Pascal está recolhido no artigo sobre armadilhas de compilação cruzada Delphi e FPC
Deixar a plataforma explicar as suas próprias falhas de carregamento
O ramo Windows do carregador enumera à mão as razões pelas quais um carregamento pode falhar, porque as distinções úteis aí, uma discrepância de arquitetura, uma dependência transitiva em falta, um caminho que não resolve, mapeiam para códigos de erro que valem a pena nomear individualmente. Fora do Windows a unidade de carregamento portátil já devolve uma string descritiva que cobre o mesmo terreno, pelo que o ramo não Windows usa-a diretamente em vez de re-derivar categorias de um número de erro que significa coisas diferentes em sistemas diferentes
Resistir ao impulso de normalizar os dois numa única mensagem é deliberado. Uma falha de carregamento é um problema de implantação, e quem lê a mensagem precisa do vocabulário próprio da plataforma para a pesquisar
Uma colisão de nomes que recorre
Mais uma armadilha, pequena e aguda. A unidade de carregamento portátil exporta um procedimento chamado UnloadLibrary, e a unidade de vinculação tem um procedimento do mesmo nome que faz a sua própria contabilidade antes de libertar o handle. Dentro desse procedimento, uma chamada não qualificada a UnloadLibrary resolve à da unidade atual, que se chama a si própria. A correção é qualificar a chamada com o nome da unidade
É a mesma forma que os problemas de sombreamento de identificadores que dominam os ports Free Pascal em geral: a unidade Windows exporta funções de mínimo e máximo de tipo inteiro que fazem sombra às de vírgula flutuante, e um tipo de sincronização que faz sombra à classe do mesmo nome, e em todos os casos a resolução depende da ordem da cláusula uses. Qualificar o ponto de chamada é a correção que não depende de alguém preservar essa ordem mais tarde
Lista de verificação de implantação
Três coisas explicam a maioria das falhas de carregamento uma vez que a aritmética de caminhos está certa. A arquitetura tem de corresponder ao processo, não à máquina, pelo que uma aplicação de 32 bits no Windows de 64 bits precisa do binário de 32 bits. A compilação com V8 tem um nome de ficheiro diferente, pelo que uma implantação que as misture parecerá correta e não carregará nada. E só uma variante pode viver num diretório de sistema de cada vez, que é uma boa razão para preferir a disposição explícita de subdiretórios a instalar qualquer coisa em todo o sistema
Para o Lazarus especificamente, ponha a biblioteca nativa sob DLLs/<cpu>-<os> em minúsculas, ao lado do executável, e será encontrada pelo primeiro passo da cadeia em todos os alvos. O exemplo de visualizador que exercita isto no Lazarus está descrito no artigo sobre o visualizador Lazarus e FPC, e o suporte atual de plataformas está listado na página de produto do PDFium Delphi component