API 集加载程序作

重要

本主题中的信息适用于所有版本的 Windows 10 及更高版本。 我们将此处的这些版本称为“Windows”,并在必要时标注任何异常。

API 集 依赖于库加载程序中的 OS 支持,将模块命名空间重定向引入库绑定过程。 API 集协定名称不命名文件。 加载程序执行从该协定名称到包含实现的主机二进制文件的运行时重定向。

当加载程序在运行时遇到对 API 集的依赖项时,它会查阅映像中的配置数据来标识该 API 集的主机二进制文件。 此配置数据称为 API 集架构。 架构作为 OS 的属性进行组合,API 集和二进制文件之间的映射可能会有所不同,具体取决于给定设备上包含的二进制文件。 架构允许在不同设备上正确路由单个二进制文件中的导入函数,即使托管实现的模块已重命名、拆分或重构。

导入如何到达实现

二进制文件可以通过两种方式访问 API 集实现,由导入表中的名称决定:

  • 直接 API 集导入。 二进制文件导入 API 集协定名称。 加载程序通过 API 集架构将该名称解析为当前设备上的主机二进制文件。
  • 旧模块导入。 二进制文件导入旧Windows模块名称,例如 samplefeature.dll。 在随附该模块的版本上,加载程序直接绑定到该模块。 在已替换它的版本中,携带相同名称的 反向转发器 会将导入重定向到 API 集,加载程序随后通过架构解析。

导入表中的哪一个名称通常由你链接的库决定,而不是由你编写的源决定。 请参阅Windows伞库。

首选面向当前版本的Windows的代码的 API 设置协定名称。 加载程序将其直接解析为主机,两者之间没有转发器。 如果需要在 API 集存在之前发布的Windows版本上运行的单个二进制文件,请导入旧模块名称。 反向转发使该二进制文件能够处理已替换旧模块的版本。

直接 API 集导入

解决方法是一个三步序列:

  1. 二进制文件导入 API 集协定名称,或将一个名称传递给 LoadLibrary。
  2. 加载程序在当前设备上的 API 集架构中查找协定,并查找架构映射到的主机二进制文件。
  3. 加载程序加载该主机二进制文件并将导入的函数绑定到主机的导出。

由于映射位于架构而不是文件系统中,因此同一导入可以解析为不同设备上的不同二进制文件:

Device api-win-core-samplefeature 映射到
包含该功能的设备 samplefeature.dll
附带重构实现的设备 samplefeaturecore.dll
不包含该功能的设备 未映射

samplefeature此处使用的名称是虚构Windows组件的说明名称。

消耗的二进制文件不知道它绑定到的主机。 这就是机制的要点:协定是稳定的,而实现它的模块可以自由地从一个设备更改为下一个设备。

在单个操作中解析协定名称的导入,不涉及中间转发器模块。 它是最有效的形式,是针对 API 集编写的代码的正常路径。

API 集名称和 .dll 后缀

由于映射保存在架构而不是磁盘上,因此以 .dll 结尾的 API 集名称不会引用该名称的文件。 .dll 部分只是一种命名约定,从模块名称在导入表中拼写的方式进行传递。 API 集名称更像是物理 DLL 文件的别名或虚拟名称。

当加载程序操作收到以或ext-开头的名称api-时,加载程序会将其路由到 API 集运行时,这是通过架构解析协定的加载程序的扩展。 API 集运行时按 API 集命名规则(而不是文件名)分析名称,因此 .dll 后缀不是解析的协定名称的一部分。 从名称中显示的名称时,请包含后缀;在导入表中显示时,请添加后缀;否则,你可以将其关闭。

加载程序通过同一架构解析协定名称、版本控制协定名称和协定别名这两种形式。 有关管理这些名称的约定,请参阅 API 设置协定名称。

名称稳定性与可用性不同

API 集名称在Windows设备中是稳定的,因为同一名称始终标识同一协定,无论识别到哪个位置。 这是命名空间的属性,不保证任何特定设备。

给定的协定可能不在设备中,或者不存在,但不能映射到主机。 名称没有任何说明。 若要了解实现是否确实存在,请参阅 “检测 API 集可用性”。

需要什么解决方法

若要通过 API 集进行调用以达到实现,必须保留以下所有内容:

  • 协定存在于当前设备上的架构中。
  • 架构将协定映射到主机二进制文件,并且可以加载该主机。
  • 主机导出二进制文件正在调用的特定函数。

当其中一个不保留时,故障图面取决于导入 API 集的方式:

导入样式 无法解析协定时的行为
静态导入 进程无法启动。 加载程序在运行任何代码之前解析静态导入。
延迟加载的导入 进程正常启动。 解析将推迟到对 API 的第一次调用,代码可以在其中处理失败。

缺少的导出与缺少的协定分开报告;导入主机不导出的函数的二进制文件失败,并出现缺少入口点错误。

成功加载的内容不会告诉你

解决方法将 协定 绑定到主机。 它不会评估该协定中单个功能的状态。

合同可以将其单独可用的功能组织到 命名组中。 组在设备上不可用,即使承载它的协定正常解析,因为加载程序以协定粒度绑定,并且绑定时不会咨询组状态。 这是故意的:拒绝将主机绑定到静态导入是致命的,因此加载程序采用宽松的路径,并将更精细的问题留给调用方。

代码的后果是,成功加载或 成功调用 LoadLibrary 不是证明特定功能可用。 使用可用性查询显式询问该问题。 请参阅 检测 API 集可用性。

可选 API 集和延迟加载

如果应用程序调用可能不存在的 API 集,则本身的可用性检查是不够的:由于静态导入,进程无法启动,因此执行永远不会到达检查。

若要使可选代码路径可访问,请配置承载可选 API 的模块以 延迟加载,或者在可用性查询成功后使用 LoadLibrary 和 GetProcAddress 动态解析目标。 有关这两种方法的详细信息,请参阅 检测 API 集可用性。

反向转发

虽然 API 集名称为跨设备的模块提供稳定的命名空间,但将每个二进制文件转换为此系统并不总是可行。 应用程序可能已常使用多年,并且重新编译其二进制文件可能不可行。 某些应用程序还需要在引入特定 API 集之前生成的系统上继续运行。

为适应这一点,不携带原始模块的版本包括一组反向转发器:承载最初在 Windows 电脑上引入的模块名称的兼容性二进制文件,以及将其导出重定向到 API 集。

完整的桌面版本附带了原始模块,因此旧模块名称的导入会像往常一样绑定到模块。 在已替换该模块的版本上,携带相同名称的反向转发器将覆盖空白。

加载程序作的行为如下所示:

  1. 加载程序与设备上不存在的旧版Windows电脑模块名称存在依赖关系。
  2. 加载程序查找承载该模块名称的反向转发器,并加载它。
  3. 反向转发器将导入的函数重定向到 API 集。
  4. 加载程序解析通过架构设置的 API,如本主题前面所述。

从概念上讲,映射如下所示:

导入的 DLL: samplefeature.dll

  • 在具有原始模块的版本上: samplefeature.dll
  • 在已替换它的版本中: samplefeature.dll 反向转发器 ->api-win-core-samplefeature ->samplefeaturecore.dll

此路径的限制是导出覆盖范围。 反向转发器只承载具有 API 集等效项的导出,因此它不一定导出原始模块执行的每个函数。 导入反向转发器函数的二进制文件不会加载失败,并出现缺少入口点错误。

反向转发也是不将成功解决方案视为证明实现存在的原因。 对旧模块名称的 GetProcAddress 调用可以返回一个有效的函数指针,该指针解析为返回错误的存根。 而是显式查询可用性。 请参阅 检测 API 集可用性。

注意

反向转发仅涵盖 Win32 API 图面的子集。 它不允许面向桌面版本的Windows的应用程序在所有Windows设备上运行。 如果二进制文件面向当前版本的 Windows,则 API 集协定名称是更直接的选择。

另见