Detetar a disponibilidade do conjunto de APIs

Em alguns casos, um dado nome de contrato de conjunto de API pode ser intencionalmente mapeado para um nome de módulo vazio em alguns dispositivos Windows. As razões para isto variam, mas um exemplo comum é que uma funcionalidade dispendiosa em termos de recursos do sistema pode ser removida do sistema operativo Windows quando configurada para um dispositivo com recursos limitados. Isso representa um desafio para os aplicativos lidarem graciosamente com recursos opcionais no nível da API.

A abordagem tradicional para testar se uma API do Win32 está disponível é usar LoadLibrary ou GetProcAddress. No entanto, estes não são um meio fiável para testar conjuntos de APIs, devido ao encaminhamento reverso. Quando o encaminhamento reverso é aplicado a uma determinada API, LoadLibrary ou GetProcAddress podem ser resolvidos para um ponteiro de função válido mesmo nos casos em que a implementação interna foi removida. Nesse caso, o ponteiro da função estará apontando para uma função de stub que simplesmente retorna um erro.

Para detetar esse caso, você pode usar a função IsApiSetImplemented para consultar a disponibilidade subjacente de uma determinada implementação de API. Este teste reporta se o conjunto de APIs está presente no esquema composto do conjunto de APIs no dispositivo em execução, e se está mapeado para um módulo de implementação.

Importante

Uma consulta de disponibilidade bem-sucedida não garante que uma chamada específica terá sucesso. Continue a tratar erros de carregamento de módulos, exportações em falta e os próprios resultados documentados de falhas da API.

O exemplo de código seguinte demonstra como usar o IsApiSetImplementado para determinar se o conjunto de APIs que contém o WTSEnumerateSessionsW está disponível no dispositivo atual antes de o chamar.

#include <windows.h>
#include <apiquery2.h>
#include <stdio.h>
#include <wtsapi32.h>

#pragma comment(lib, "OneCore.lib")
#pragma comment(lib, "Wtsapi32.lib")

int __cdecl wmain(int /* argc */, PCWSTR /* argv */ [])
{
    PWTS_SESSION_INFOW pInfo = nullptr;
    DWORD count = 0;

    if (!IsApiSetImplemented("ext-ms-win-session-wtsapi32-l1-1-0"))
    {
        wprintf(L"ext-ms-win-session-wtsapi32-l1-1-0 is not available.\n");
        return 0;
    }

    if (WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pInfo, &count))
    {
        wprintf(L"SessionCount = %lu\n", count);

        for (DWORD i = 0; i < count; i++)
        {
            PWTS_SESSION_INFOW pCurInfo = &pInfo[i];
            wprintf(L"    %ls: ID = %lu, state = %d\n", pCurInfo->pWinStationName,
                pCurInfo->SessionId, static_cast<int>(pCurInfo->State));
        }

        WTSFreeMemory(pInfo);
    }
    else
    {
        wprintf(L"WTSEnumerateSessionsW failure: %lu\n", GetLastError());
    }

    return 0;
}

IsApiSetImplemented é declarado em apiquery2.h, e o caminho de ligação do SDK público normal fornece-o a partir do OneCore.lib. Essa biblioteca é separada de qualquer biblioteca de importação necessária para chamar a API de destino opcional em si.

O exemplo acima liga Wtsapi32.lib para uma importação estática, o que mantém o exemplo curto. Uma aplicação de produção que tenha de correr onde o conjunto de API está ausente também deve aplicar a orientação em Manter o caminho opcional do código acessível.

Escolha o nome da consulta

Passe o nome do conjunto de APIs que está a testar. Os nomes dos conjuntos de APIs são convencionalmente escritos sem sufixo .dll , e os exemplos nesta página usam essa forma. O sufixo não faz parte do nome do conjunto da API.

Para encontrar o nome a passar, consulte a tabela de Requisitos na página de referência da API que pretende chamar. Se essa tabela tiver uma linha de API definida , dá o nome do contrato. Caso contrário, use a linha DLL , mas apenas quando o nome lá aí for ele próprio um nome de contrato a começar por api- ou ext-; um nome físico de módulo como Wtsapi32.dll não é um contrato, e ao consultá-lo devolve FALSE.

A forma do nome depende de como a API é endereçada.

Superfície API Formulário de consulta Example
Grupo nomeado <contract>~<group> api-win-core-samplefeature~AdvancedOperations
Grupo padrão Nome contratual, sem ~Default api-win-core-samplefeature
Contrato versionado Nome completo do contrato versionado ext-ms-win-core-samplefeature-l1-1-0

Os samplefeature nomes são nomes ilustrativos para um componente fictício do Windows. O prefixo do nome (api- ou ext-) não tem influência no comportamento de disponibilidade.

Para um grupo nomeado, uma consulta bem-sucedida significa que o grupo existe, o seu contrato é mapeado para um módulo de implementação, esse host é utilizável no ambiente de execução atual, o grupo não está desativado e qualquer funcionalidade do sistema associada ao grupo está ativada. Uma consulta que utiliza um alias contratual aplica as verificações do contrato e do hospedeiro.

Se o cabeçalho público da API fornecer um Is<APIName>Present assistente, prefira esse ajudante. Já contém o nome correto para o conjunto ou grupo de APIs que transporta a API.

O resultado de uma consulta é granular por contrato ou grupo, não granular por funções. Dois helpers apoiados pelo mesmo grupo nomeado retornam sempre o mesmo resultado, mesmo que cada helper tenha o nome de uma API diferente.

Mantenha o caminho opcional do código acessível

Uma verificação de disponibilidade não pode proteger o arranque do processo se a API opcional estiver ligada como uma importação estática. O loader resolve importações estáticas antes do seu código ser executado, por isso um módulo em falta falha o processo antes de a execução chegar à verificação.

Use uma destas abordagens:

  • Configure o módulo que transporta a API opcional para carregamento por atraso. O carregamento por atraso é uma definição do linker: especificar /DELAYLOAD:<module> e ligar delayimp.lib. Especifique o nome do módulo que aparece na tabela de importação do seu binário, que pode ser o nome clássico da DLL em vez do nome do contrato do conjunto da API. Para o exemplo anterior, esse nome é WTSAPI32.dll.
  • Ou resolver o destino dinamicamente com LoadLibrary e GetProcAddress depois de a consulta de disponibilidade ter sucesso.

Não use LoadLibrary ou GetProcAddress como substituto da consulta de disponibilidade. Eles respondem a perguntas de módulos e exportam, não avaliam o estado do grupo nomeado, e podem resolver para um stub de encaminhamento inverso como descrito anteriormente. Usa-os depois da consulta, para gerir o módulo separado e as verificações de exportação.

Comportamento em versões anteriores do Windows

A implementação fornecida pelo OneCore.lib seleciona um mecanismo de consulta disponível em tempo de execução, pelo que uma aplicação que chama o IsApiSetImplementado pode ainda ser executada num sistema anterior ao suporte de consultas subjacente.

Num sistema onde não existe mecanismo de consulta disponível:

  • Um nome qualificado por grupo que contém ~ devolve FALSE. Um sistema que não consegue avaliar grupos nomeados não pode reportar que um grupo está disponível.
  • Um nome que não seja qualificado para o grupo pode devolver TRUE. Isto preserva a compatibilidade com aplicações que forneciam as suas próprias DLLs de encaminhador nas primeiras versões do Windows, onde o contrato era de facto cumprido por esse encaminhador.

Não use uma consulta de disponibilidade de API como verificação de segurança ou autorização.

Consulte também