在某些情况下,给定的 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 集可用性查询作为安全或授权检查。