Operação do carregador de conjunto de API

Importante

As informações neste tópico se aplicam a todas as versões do Windows 10 e posteriores. Vamos nos referir a essas versões aqui como "Windows", chamando todas as exceções quando necessário.

Os conjuntos de API dependem do suporte do sistema operacional no carregador de biblioteca para introduzir um redirecionamento de namespace de módulo no processo de associação de biblioteca. Um nome de contrato de conjunto de API não nomeia um arquivo. O carregador executa um redirecionamento em tempo de execução desse nome de contrato para o binário do host que contém a implementação.

Quando o carregador encontra uma dependência em um conjunto de API em tempo de execução, ele consulta os dados de configuração na imagem para identificar o binário do host para esse conjunto de API. Esses dados de configuração são chamados de de esquema de conjunto de API. O esquema é montado como uma propriedade do sistema operacional e o mapeamento entre conjuntos de API e binários pode ser diferente dependendo de quais binários são incluídos em um determinado dispositivo. O esquema é o que permite que uma função importada em um único binário seja roteada corretamente em diferentes dispositivos, mesmo quando o módulo que hospeda a implementação tiver sido renomeado, dividido ou refatorado.

Como as importações chegam a uma implementação

Um binário pode alcançar uma implementação de conjunto de API de duas maneiras, decidida pelo nome em sua tabela de importação:

  • Importação de conjunto de API direta. O binário importa um nome de contrato do conjunto de API. O carregador resolve esse nome por meio do esquema de conjunto de API para o binário de host no dispositivo atual.
  • Importação de módulo herdado. O binário importa um nome de módulo Windows herdado, como samplefeature.dll. Em uma edição que envia esse módulo, o carregador é associado diretamente a ele. Em uma edição que a substituiu, um encaminhador reverso que carrega o mesmo nome redireciona a importação para um conjunto de API, que o carregador resolve por meio do esquema.

Qual desses nomes acaba em sua tabela de importação geralmente é determinado pela biblioteca com a qual você vincula e não pela origem que você escreve. Consulte Windows bibliotecas de guarda-chuvas.

Prefira o nome do contrato do conjunto de API para o código destinado às versões atuais do Windows. O carregador o resolve diretamente para o host, sem nenhum encaminhador no meio. Importe o nome do módulo herdado quando você precisar de um único binário que também seja executado em Windows versões lançadas antes da existência do conjunto de API. O encaminhamento reverso mantém esse binário funcionando em edições em que o módulo herdado foi substituído.

Importação de conjunto de API Direta

A resolução é uma sequência de três etapas:

  1. Seu binário importa um nome de contrato de conjunto de API ou passa um para LoadLibrary.
  2. O carregador procura o contrato no esquema de conjunto de API no dispositivo atual e localiza o binário do host para o qual o esquema o mapeia.
  3. O carregador carrega o binário do host e associa a função importada à exportação do host.

Como o mapeamento reside no esquema e não no sistema de arquivos, a mesma importação pode ser resolvida para binários diferentes em dispositivos diferentes:

Dispositivo api-win-core-samplefeature mapas para
Um dispositivo que inclui o recurso samplefeature.dll
Um dispositivo que fornece uma implementação refatorada samplefeaturecore.dll
Um dispositivo que não inclui o recurso Não mapeado

Os samplefeature nomes usados aqui são nomes ilustrativos para um componente de Windows fictício.

O binário consumidor não sabe a qual host ele estava associado. Esse é o ponto do mecanismo: o contrato é estável, enquanto o módulo que o implementa é livre para alterar de um dispositivo para o próximo.

Uma importação de um nome de contrato é resolvida em uma única operação, sem nenhum módulo de encaminhador intermediário envolvido. É a forma mais eficiente e o caminho normal para código gravado em conjuntos de API.

Nomes de conjunto de API e o sufixo .dll

Como os mapeamentos são mantidos no esquema e não no disco, um nome de conjunto de API que termina com .dll não se refere a um arquivo desse nome. A parte.dll é apenas uma convenção de nomenclatura, transmitida da maneira como os nomes de módulo são escritos em uma tabela de importação. O nome do conjunto de API é mais como um alias ou um nome virtual para um arquivo DLL físico.

Quando uma operação de carregador recebe um nome que começa com api- ou ext-, o carregador o roteia para o runtime do conjunto de API, uma extensão do carregador que resolve contratos por meio do esquema. O runtime do conjunto de API analisa o nome por regras de nomenclatura de conjunto de API em vez de como um nome de arquivo, de modo que o sufixo.dll não faz parte do nome do contrato que é resolvido. Inclua o sufixo quando você estiver trabalhando com base em um nome como ele aparece em uma tabela de importação; caso contrário, você pode deixá-lo fora.

O carregador resolve ambas as formas de nome do contrato, um nome de contrato com versão e um alias de contrato, por meio do mesmo esquema. Para as convenções que regem esses nomes, consulte nomes de contrato definidos pela API.

A estabilidade do nome não é a mesma que a disponibilidade

Um nome de conjunto de API é estável em Windows dispositivos, no sentido de que o mesmo nome sempre identifica o mesmo contrato onde quer que seja reconhecido. Essa é uma propriedade do namespace, não uma garantia sobre qualquer dispositivo específico.

Um determinado contrato pode estar ausente de um dispositivo ou presente, mas não mapeado para um host. Nada sobre o nome informa qual. Para descobrir se a implementação está realmente lá, consulte Detectar disponibilidade do conjunto de API.

Qual resolução requer

Para uma chamada por meio de um conjunto de API para alcançar uma implementação, todos os seguintes devem ser retenção:

  • O contrato está presente no esquema no dispositivo atual.
  • O esquema mapeia o contrato para um binário host e esse host pode ser carregado.
  • O host exporta a função específica que seu binário está chamando.

Quando um deles não se mantém, em que as superfícies de falha dependem de como você importou o conjunto de API:

Estilo de importação Comportamento quando o contrato não pode ser resolvido
Importação estática O processo não é iniciado. O carregador resolve importações estáticas antes que qualquer um dos códigos seja executado.
Importação carregada com atraso O processo é iniciado normalmente. A resolução é adiada para a primeira chamada para a API, na qual seu código pode lidar com a falha.

Uma exportação ausente é relatada separadamente de um contrato ausente; um binário que importa uma função que o host não exporta falha com um erro de ponto de entrada ausente.

O que uma carga bem-sucedida não lhe diz

A resolução associa um contrato a um host. Ele não avalia o estado de uma funcionalidade individual dentro desse contrato.

Um contrato pode organizar seus recursos disponíveis individualmente em grupos nomeados. Um grupo pode ficar indisponível em um dispositivo, mesmo que o contrato que o carrega resolva normalmente, pois o carregador se associa à granularidade do contrato e não consulta o estado do grupo quando ele é associado. Isso é deliberado: recusar-se a associar um host é fatal a uma importação estática, portanto, o carregador assume o caminho permissivo e deixa a pergunta mais fina para o chamador.

A consequência para seu código é que uma carga bem-sucedida ou uma chamada loadLibrary bem-sucedida não é uma evidência de que uma funcionalidade específica está disponível. Faça essa pergunta explicitamente com uma consulta de disponibilidade. Consulte Detectar disponibilidade do conjunto de API.

Conjuntos de API opcionais e atraso no carregamento

Se seu aplicativo chamar um conjunto de API que pode não estar presente, uma verificação de disponibilidade por conta própria não é suficiente: com uma importação estática, o processo falha ao iniciar, portanto, a execução nunca chega à verificação.

Para manter o caminho de código opcional acessível, configure o módulo que carrega a API opcional para o carregamento de atraso ou resolva o destino dinamicamente com LoadLibrary e GetProcAddress depois que uma consulta de disponibilidade for bem-sucedida. Para obter os detalhes de ambas as abordagens, consulte Detectar disponibilidade do conjunto de API.

Encaminhamento reverso

Embora os nomes de conjuntos de API forneçam um namespace estável para módulos entre dispositivos, nem sempre é prático converter todos os binários nesse sistema. Um aplicativo pode estar em uso comum há muitos anos e recompilar seus binários pode não ser viável. Alguns aplicativos também precisam continuar em execução em sistemas que foram criados antes da introdução de conjuntos de API específicos.

Para acomodar isso, as edições que não carregam os módulos originais incluem um conjunto de encaminhadores reversos: binários de compatibilidade que carregam os nomes de módulo originalmente introduzidos em computadores Windows e que redirecionam suas exportações para conjuntos de API.

Uma edição de área de trabalho completa fornece os módulos originais, portanto, uma importação de um nome de módulo herdado se associa ao módulo como sempre fez. Em uma edição que substituiu esse módulo, o encaminhador inverso que carrega o mesmo nome cobre a lacuna.

A operação do carregador se comporta da seguinte maneira:

  1. O carregador é apresentado com uma dependência em um nome herdado do módulo do computador Windows que não está presente no dispositivo.
  2. O carregador localiza um encaminhador reverso que carrega o nome do módulo e o carrega.
  3. O encaminhador reverso redireciona a função importada para um conjunto de API.
  4. O carregador resolve esse conjunto de API por meio do esquema, conforme descrito anteriormente neste tópico.

Conceitualmente, o mapeamento tem esta aparência:

DLL importada: samplefeature.dll

  • Em uma edição que tem o módulo original: samplefeature.dll
  • Em uma edição que a substituiu: samplefeature.dll encaminhador reverso – –>api-win-core-samplefeature>samplefeaturecore.dll

O limite nesse caminho é a cobertura de exportação. Um encaminhador reverso carrega apenas as exportações que têm equivalentes definidos pela API, portanto, ele não necessariamente exporta todas as funções que o módulo original fez. Um binário que importa uma função que o encaminhador reverso não carrega falha ao carregar com um erro de ponto de entrada ausente.

O encaminhamento reverso também é um motivo para não tratar uma resolução bem-sucedida como prova de que uma implementação está presente. Uma chamada GetProcAddress em relação a um nome de módulo herdado pode retornar um ponteiro de função válido que é resolvido para um stub retornando um erro. Em vez disso, consulte a disponibilidade explicitamente. Consulte Detectar disponibilidade do conjunto de API.

Nota

O encaminhamento reverso abrange apenas um subconjunto da superfície da API Win32. Ele não permite que aplicativos destinados a versões de área de trabalho de Windows sejam executados em todos os dispositivos Windows. Se o binário direcionar as versões atuais do Windows, o nome do contrato do conjunto de API será a opção mais direta.

Consulte também