检测 API 集可用性

在某些情况下,给定的 API 集协定名称可能会有意映射到某些Windows设备上的空模块名称。 原因各不相同,但一个常见示例是,在为资源受限的设备配置时,系统资源方面的成本高昂功能可能会从Windows OS 中删除。 这为应用程序在 API 级别正常处理可选功能带来了挑战。

用于测试 Win32 API 是否可用的传统方法是使用 LoadLibrary 还是 GetProcAddress。 但是,由于 反向转发,这些不是测试 API 集的可靠方法。 如果反向转发应用于给定的 API, 则 LoadLibrary 或 GetProcAddress 可能会解析为有效的函数指针,即使在删除内部实现的情况下也是如此。 在这种情况下,函数指针将指向仅返回错误的存根函数。

为了检测这种情况,可以使用 IsApiSetImplemented 函数来查询给定 API 实现的基础可用性。 此测试报告 API 集是否存在于正在运行的设备上的组合 API 集架构中,以及它是否映射到实现模块。

Important

成功的可用性查询不能保证特定调用会成功。 继续处理模块加载错误、缺少导出以及 API 自己的记录失败结果。

下面的代码示例演示如何使用 IsApiSetImplemented 来确定包含 WTSEnumerateSessionsW 的 API 集在调用之前是否在当前设备上可用。

#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 在 apiquery2.h 中声明,普通公共 SDK 链接路径从 OneCore.lib 提供它。 该库独立于调用可选目标 API 本身所需的任何导入库。

上面的示例链接 了 Wtsapi32.lib 进行静态导入,使示例保持简短。 必须在缺少 API 集的位置运行的生产应用程序还应在 “保留可选代码路径”中应用指导。

选择查询名称

传递要测试的 API 集的名称。 API 集名称通常不 .dll 带后缀编写,此页上的示例使用该窗体。 后缀不是 API 集名称的一部分。

若要查找要传递的名称,请参阅要调用的 API 的参考页上的 “要求 ”表。 如果该表具有 API 集 行,则会提供协定名称。 否则,请使用 DLL 行,但只有当名称本身有一个协定名称开头 api- 或 ext-;物理模块名称(如 Wtsapi32.dll )不是协定,并且查询它返回 FALSE 时。

名称的形式取决于 API 的寻址方式。

API 接口面 查询表单 Example
命名组 <contract>~<group> api-win-core-samplefeature~AdvancedOperations
默认组 协定别名,不带 ~Default api-win-core-samplefeature
版本控制协定 完整的版本控制协定名称 ext-ms-win-core-samplefeature-l1-1-0

这些samplefeature名称是虚构Windows组件的说明名称。 名称前缀(api- 或 ext-)在可用性行为中不起作用。

对于命名组,成功的查询意味着该组存在,其协定将映射到实现模块,该主机在当前执行环境中可用,组未禁用,并且启用了与该组关联的任何系统功能。 使用协定别名的查询将应用协定和主机检查。

如果 API 的公共标头提供 Is<APIName>Present 帮助程序,则首选该帮助程序。 它已包含承载 API 的 API 集或组的正确名称。

查询的结果是协定或组粒度,而不是函数粒度。 由同一命名组支持的两个帮助程序始终返回相同的结果,即使每个帮助程序都为不同的 API 命名。

使可选代码路径保持可访问性

如果可选 API 作为静态导入链接,可用性检查无法保护进程启动。 加载程序在代码运行之前解析静态导入,因此在执行到达检查之前,缺少的模块会失败。

使用以下方法之一:

  • 配置承载可选 API 的模块,以便 延迟加载。 延迟加载是链接器设置:指定 /DELAYLOAD:<module> 和链接 delayimp.lib。 指定二进制导入表中显示的模块名称,这可能是经典 DLL 名称,而不是 API 集协定名称。 对于前面的示例,该名称 WTSAPI32.dll。
  • 或者,在可用性查询成功后使用 LoadLibrary 和 GetProcAddress 动态解析目标。

不要将 LoadLibrary 或 GetProcAddress 用作可用性查询的替代项。 它们回答模块和导出问题,它们不评估命名的组状态,并且可以解决到前面所述的反向转发存根。 在查询后使用它们来处理单独的模块和导出检查。

早期版本的Windows的行为

OneCore.lib 提供的实现在运行时选择一种可用的查询机制,因此调用 IsApiSetImplemented 的应用程序仍然可以在预先提供基础查询支持的系统上运行。

在没有可用的查询机制的系统上:

  • 包含 FALSE 的~组限定名称。 无法评估命名组的系统无法报告组是否可用。
  • 非组限定的名称可以返回 TRUE。 这可以保持与在早期版本的 Windows 上提供自己的转发器 DLL 的应用程序的兼容性,而该扩展程序实际上满足该协定。

不要使用 API 集可用性查询作为安全或授权检查。

另见