Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Importante
As informações neste tópico aplicam-se a todas as versões do Windows 10 e posteriores. Referir-nos-emos a essas versões aqui como "Windows", chamando a atenção para quaisquer exceções quando necessário.
Os conjuntos de API dependem do suporte ao sistema operativo no carregador de bibliotecas para introduzir um redirecionamento de espaço de nomes de módulo no processo de ligação de bibliotecas. Um nome de contrato de API não nomeia um ficheiro. O carregador realiza uma redireção em tempo de execução desse nome de contrato para o binário anfitrião que contém a implementação.
Quando o loader encontra uma dependência numa API definida em tempo de execução, consulta os dados de configuração na imagem para identificar o binário anfitrião desse conjunto de APIs. Esses dados de configuração são chamados de esquema de conjunto de API . O esquema é montado como uma propriedade do SO, e o mapeamento entre conjuntos de APIs e binários pode variar dependendo dos binários incluídos num dado dispositivo. O esquema é o que permite que uma função importada num único binário seja corretamente encaminhada em diferentes dispositivos, mesmo quando o módulo que hospeda a implementação foi renomeado, dividido ou refatorado.
Como as importações chegam à implementação
Um binário pode alcançar uma implementação de conjunto de API de duas formas, decididas pelo nome na sua tabela de importação:
- Importação direta de conjuntos de API. O binário importa um nome de contrato de API definido. O loader resolve esse nome através do esquema API definido para o binário host no dispositivo atual.
- Importação de módulos legados. O binário importa um nome de módulo Windows legado, como samplefeature.dll. Numa edição que envia esse módulo, o carregador liga-se diretamente a ele. Numa edição que a substituiu, um encaminhador reverso com o mesmo nome redireciona a importação para um conjunto de API, que o carregador resolve depois através do esquema.
Qual desses nomes acaba na tua tabela de importação é geralmente determinado pela biblioteca contra a qual ligas e não pela fonte que escreves. Consulte bibliotecas guarda-chuva do Windows.
Prefiro o nome do contrato definido pela API para código que visa as versões atuais do Windows. O carregador resolve diretamente para o anfitrião, sem encaminhador pelo meio. Importa o nome do módulo legado quando precisares de um único binário que também corra em versões do Windows lançadas antes da existência do conjunto de APIs. O encaminhamento reverso mantém esse binário a funcionar nas edições onde o módulo legado foi substituído.
Importação direta de conjuntos de API
A resolução é uma sequência de três passos:
- O seu binário importa uma API que define o nome do contrato, ou passa um para a LoadLibrary.
- O loader procura o contrato no esquema do conjunto de API no dispositivo atual e encontra o binário anfitrião para onde o esquema o mapeia.
- O loader carrega esse 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 ficheiros, a mesma importação pode ser resolvida para binários diferentes em dispositivos distintos:
| Dispositivo |
api-win-core-samplefeature mapas para |
|---|---|
| Um dispositivo que inclui a funcionalidade | samplefeature.dll |
| Um dispositivo que disponibiliza uma implementação refatorada | samplefeaturecore.dll |
| Um dispositivo que não inclui essa funcionalidade | Não mapeada |
Os samplefeature nomes usados aqui são nomes ilustrativos para um componente fictício do Windows.
O sistema binário consumidor não sabe a que hospedeiro estava ligado. Esse é o objetivo do mecanismo: o contrato é estável, enquanto o módulo que o implementa é livre para mudar de um dispositivo para outro.
A importação de um nome de contrato é resolvida numa única operação, sem módulo de encaminhamento intermédio envolvido. É a forma mais eficiente e o caminho normal para código escrito contra conjuntos de APIs.
Nomes de conjuntos de API e o sufixo .dll
Como os mapeamentos são mantidos no esquema em vez de no disco, um nome de conjunto de API que termina em .dll não se refere a um ficheiro com esse nome. A .dll parte é apenas uma convenção de nomenclatura, herdada da forma como os nomes dos módulos são escritos numa tabela de importação. O nome do conjunto da API é mais parecido com um alias, ou um nome virtual, para um ficheiro DLL físico.
Quando uma operação de carregador recebe um nome que começa por api- ou ext-, o carregador encaminha-o para o tempo de execução do conjunto de API, uma extensão do carregador que resolve contratos através do esquema. O runtime do conjunto de API analisa o nome pelas regras de nomeação do conjunto de APIs em vez de como nome de ficheiro, por isso o sufixo.dll não faz parte do nome do contrato que é resolvido. Inclua o sufixo quando estiver a trabalhar a partir de um nome tal como aparece numa tabela de importação; caso contrário, pode deixá-lo de fora.
O carregador resolve ambas as formas de nome do contrato, um nome de contrato versionado e um alias contratual, através do mesmo esquema. Para as convenções que regem estes nomes, veja nomes de contratos de conjunto de API.
Estabilidade do nome não é o mesmo que disponibilidade
Um nome conjunto de API é estável entre dispositivos Windows, no sentido em que o mesmo nome identifica sempre o mesmo contrato onde quer que seja reconhecido. Isso é uma propriedade do namespace, não uma garantia sobre nenhum dispositivo em particular.
Um dado contrato pode estar ausente de um dispositivo, ou estar presente mas não mapeado para um hospedeiro. Nada no nome diz qual. Para saber se a implementação está realmente lá, consulte Detetar disponibilidade de conjuntos de API.
O que a resolução requer
Para uma chamada através de uma API definida para chegar a uma implementação, todas as seguintes condições têm de se cumprir:
- O contrato está presente no esquema do 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 o seu binário está a chamar.
Quando uma dessas falhas não se confirma, onde a falha surge depende de como 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 começa. O loader resolve importações estáticas antes de qualquer código ser executado. |
| Importação carregada por atraso | O processo começa normalmente. A resolução é adiada para a primeira chamada à API, onde o seu código pode lidar com a falha. |
Uma exportação em falta é reportada separadamente de um contrato em falta; um binário que importa uma função que o host não exporta falha com um erro de ponto de entrada em falta.
O que uma carga bem-sucedida não te diz
A resolução vincula um contrato a um anfitrião. Não avalia o estado da capacidade individual dentro desse contrato.
Um contrato pode organizar as suas capacidades individuais disponíveis em grupos nomeados. Um grupo pode estar indisponível num dispositivo mesmo que o contrato que o transporta se resolva normalmente, porque o carregador liga à granularidade do contrato e não consulta o estado do grupo quando se liga. Isso é deliberado: recusar vincular um host é fatal para uma importação estática, por isso o loader segue o caminho permissivo e deixa a questão mais fina para o chamador.
A consequência para o teu código é que um carregamento bem-sucedido, ou uma chamada LoadLibrary bem-sucedida, não é prova de que uma funcionalidade específica está disponível. Faça essa pergunta explicitamente com uma consulta de disponibilidade. Veja Detetar disponibilidade de conjuntos de API.
Conjuntos opcionais de APIs e carregamento por atraso
Se a sua aplicação chamar um conjunto de API que pode não estar presente, uma verificação de disponibilidade por si só não é suficiente: com uma importação estática o processo falha em iniciar, por isso a execução nunca chega à verificação.
Para manter o caminho de código opcional acessível, configure o módulo que transporta a API opcional para carregamento por atraso, ou resolva o destino dinamicamente com LoadLibrary e GetProcAddress após uma consulta de disponibilidade bem-sucedida. Para detalhes de ambas as abordagens, consulte Detectar disponibilidade de conjuntos de API.
Encaminhamento reverso
Embora os nomes dos conjuntos de API forneçam um espaço de nomes estável para módulos entre dispositivos, nem sempre é prático converter todos os binários para este sistema. Uma aplicação pode ter sido de uso comum durante muitos anos, e recompilar os seus binários pode não ser viável. Algumas aplicações também precisam de continuar a correr em sistemas que foram construídos antes da introdução de conjuntos específicos de APIs.
Para acomodar isso, as edições que não transportam os módulos originais incluem um conjunto de reverse forwarders: binários de compatibilidade que transportam os nomes dos módulos originalmente introduzidos nos PCs Windows, e que redirecionam as suas exportações para conjuntos de APIs.
Uma edição desktop completa inclui os módulos originais, por isso a importação de um nome de módulo legado liga-se ao módulo como sempre fez. Numa edição que substituiu esse módulo, o reencaminhador reverso com o mesmo nome cobre a lacuna.
A operação do carregador se comporta assim:
- O loader apresenta-se com uma dependência de um nome legado de módulo Windows PC que não está presente no dispositivo.
- O carregador localiza um reencaminhador de marcha-atrás que tem esse nome de módulo e carrega-o.
- O reencaminhador reverso redireciona a função importada para um conjunto de APIs.
- O loader resolve esse conjunto de API através do esquema, conforme descrito anteriormente neste tópico.
Conceptualmente, o mapeamento é assim:
DLL importado: samplefeature.dll
- Numa edição que tenha o módulo original: samplefeature.dll
- Numa edição que o substituiu: samplefeature.dll forward de marcha atrás ->
api-win-core-samplefeature>samplefeaturecore.dll
O limite neste percurso é a cobertura de exportação. Um reencaminhador reverso transporta apenas as exportações que têm equivalentes definidos pela API, por isso não exporta necessariamente todas as funções que o módulo original exportava. Um binário que importa uma função que o redirecionador reverso não carrega falha ao carregar com um erro de entrada em falta.
O encaminhamento reverso é também uma razão para não tratar uma resolução bem-sucedida como prova de que existe uma implementação. Uma chamada GetProcAddress contra um nome de módulo legado pode devolver um ponteiro de função válido que se resolve para um stub que devolve um erro. Consulta a disponibilidade explicitamente em vez disso. Veja Detetar disponibilidade de conjuntos de API.
Observação
O encaminhamento reverso cobre apenas um subconjunto da superfície da API Win32. Não permite que aplicações que visam versões desktop do Windows corram em todos os dispositivos Windows. Se o seu binário tem como alvo as versões atuais do Windows, o nome do contrato definido pela API é a escolha mais direta.