从驱动程序存储运行

使用“从驱动程序存储运行”的 INF 意味着该 INF 使用 DIRID 13 指定安装时驱动程序包文件的位置。

对于由 INF 负载的“从驱动程序存储运行”文件,INF 中该文件的 SourceDisksFiles 条目中列出的 subdir 必须与 INF 中该文件的 DestinationDirs 条目中列出的 subdir 匹配。

此外,CopyFiles 指令不能用于重命名从驱动程序存储运行的文件。 这些限制是必需的,这样在设备上安装 INF 才不会导致在驱动程序存储目录中创建新文件。

由于 SourceDisksFiles 条目无法具有多个文件名相同的条目,并且 CopyFiles 不能用于重命名文件,因此 INF 引用的每个“从驱动程序存储运行”文件都必须具有唯一的文件名。

从 Windows 10 1709 开始,驱动程序包通常支持“从驱动程序存储运行”。 但是,某些设备堆栈可能会对你需要提供以插入到该堆栈的文件施加额外限制。 例如,下面的设备堆栈在 Windows 10 1803 之前不支持“从驱动程序存储运行”:

如果要提供插入到特定设备堆栈的二进制文件,请参阅要插入到的特定设备堆栈的相关文档,看看它是否支持提供到二进制文件的完整文件路径,以及该完整文件路径是否有任何限制。 如果它支持提供到二进制文件的完整文件路径,且对该路径没有限制,则它应支持文件“从驱动程序存储运行”。

从驱动程序存储动态查找和加载文件

有时,组件需要加载属于使用“从驱动程序存储运行”的驱动程序包的一部分的文件。 不应对这些驱动程序包文件的路径进行硬编码,因为它们在不同的驱动程序包版本、不同 OS 版本等之间可能有所不同。当需要加载驱动程序包文件时,应该使用下面介绍的一些范例来动态发现和加载这些驱动程序包文件。

查找并加载同一驱动程序包中的文件

当驱动程序包中的文件需要从同一驱动程序包加载另一个文件时,动态发现该文件的一个可用选项是确定运行该文件的目录并相对于该目录加载另一个文件。

从 Windows 10 版本 1803 和更高版本上的驱动程序存储运行的 WDM 或 KMDF 驱动程序应,需要访问其设备驱动程序时,应调用 IoGetDriverDirectory,并将 DriverDirectoryImage 设置为目录类型,以获取用于加载驱动程序的目录路径。 或者,对于需要支持 Windows 10 版本 1803 之前的 OS 版本的驱动程序,使用 IoQueryFullDriverPath 查找驱动程序的路径,获取用于加载驱动程序的目录路径,并查找相对于该路径的文件。 如果内核模式驱动程序是 KMDF 驱动程序,则它可以使用 WdfDriverWdmGetDriverObject 来检索要传递到 IoQueryFullDriverPath 的 WDM 驱动程序对象。

用户模式二进制文件可以使用 GetModuleHandleExWGetModuleFileNameW 来确定驱动程序是从何处加载的。 例如,UMDF 驱动程序二进制文件可能会执行以下操作:

bRet = GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
                         (PCWSTR)&DriverEntry,
                         &handleModule);
if (bRet) {
    charsWritten = GetModuleFileNameW(handleModule,
                                      path,
                                      pathLength);
    …

查找并加载任意驱动程序包中的文件

在某些情况下,驱动程序包可能包含应该由另一驱动程序包中的二进制文件加载或由某个用户模式组件加载的文件。 如果此方法比上述从同一驱动程序包加载文件的方法更可取,则此方法也可用于来自同一驱动程序包的文件。

以下是可能涉及从驱动程序包加载文件的几个方案示例:

  • 驱动程序包中的用户模式 DLL 提供一个用来与驱动程序包中的驱动程序通信的接口。

  • 扩展驱动程序包包含一个由基准驱动程序包中的驱动程序加载的配置文件。

在这些情况下,驱动程序包应在设备或设备接口上设置某种状态,以指示预期要加载的文件的路径。

驱动程序包通常会使用 HKR AddReg 来设置此状态。 在此示例中,应该假定:对于 ExampleFile.dll,驱动程序包有一个没有任何 subdirSourceDisksFiles 条目。 这样就会将文件置于驱动程序包目录的根目录中。 还应假设 CopyFiles 指令的 DestinationDirs 指定 dirid 13。

下面是一个 INF 示例,演示了如何将其设置为设备状态:

[ExampleDDInstall.HW]
AddReg = Example_DDInstall.AddReg

[Example_DDInstall.AddReg]
HKR,,ExampleValue,,%13%\ExampleFile.dll

而将其设置为设备接口状态的 INF 示例为:

[ExampleDDInstall.Interfaces]
AddInterface = {<fill in an interface class GUID for an interface exposed by the device>},,Example_Add_Interface_Section

[Example_Add_Interface_Section]
AddReg = Example_Add_Interface_Section.AddReg

[Example_Add_Interface_Section.AddReg]
HKR,,ExampleValue,,%13%\ExampleFile.dll

前面的示例使用空的标志值,这会导致生成 REG_SZ 注册表值。 这样就会将 %13% 转换成完全限定的用户模式文件路径。 在许多情况下,最好是将路径设置为某个环境变量的相对值。 如果使用标志值 0x20000,则注册表值为类型 REG_EXPAND_SZ,而 %13% 则会转换为一个包含相应环境变量的路径,该变量用于抽象路径的位置。 检索此注册表值时,请调用 ExpandEnvironmentStrings 来解析路径中的环境变量。

如果此值需由内核模式组件读取,则此值应该是 REG_SZ 值。 内核模式组件在读取该值时应该在其前面预置 \??\,然后再将其传递给 ZwOpenFile 之类的 API。

当此设置成为设备状态的一部分时,如果要访问它,则应用程序必须先找到设备的标识。 用户模式代码可以使用 CM_Get_Device_ID_List_SizeCM_Get_Device_ID_List 获取设备的列表(按需筛选)。 该设备列表可能包含多个设备,因此请先搜索相应的设备,然后再从设备读取状态。 例如,在查找符合特定条件的设备时,请调用 CM_Get_DevNode_Property 来检索设备的属性。

找到正确的设备以后,请调用 CM_Open_DevNode_Key 来获取存储设备状态的注册表位置的句柄。

内核模式代码应检索具有状态的设备的 PDO(物理设备对象)并调用 IoOpenDeviceRegistryKey。 内核模式代码检索设备的 PDO 的一种可能方法是发现设备公开的已启用接口,并使用 IoGetDeviceObjectPointer 检索设备对象。

若要在此设置是设备接口状态时访问它,可以通过用户模式代码来调用 CM_Get_Device_Interface_List_SizeCM_Get_Device_Interface_List

另外,可以使用 CM_Register_Notification 来获取设备接口的到达和删除通知,这样就可以在启用接口时通知代码,让代码随后检索状态。 在设备接口类(在上述 API 中使用)中可能有多个设备接口。 检查这些接口,确定哪个接口是适合读取设置的接口。

找到正确的设备接口以后,调用 CM_Open_Device_Interface_Key

内核模式代码可以检索从其获取状态的设备接口的符号链接名称。 为此,请调用 IoRegisterPlugPlayNotification,以便注册获取相应设备接口类的设备接口通知。 也可调用 IoGetDeviceInterfaces,获取系统中当前设备接口的列表。 在设备接口类(在上述 API 中使用)中可能有多个设备接口。 检查这些接口,确定哪个接口是应该读取设置的正确接口。

找到适当的符号链接名称以后,请调用 IoOpenDeviceInterfaceRegistryKey,以便检索在其中存储了设备接口状态的注册表位置的句柄。

注意

CM_GETIDLIST_FILTER_PRESENT 标志与 CM_Get_Device_ID_List_SizeCM_Get_Device_ID_List 配合使用,或者将 CM_GET_DEVICE_INTERFACE_LIST_PRESENT 标志与 CM_Get_Device_Interface_List_SizeCM_Get_Device_Interface_List 配合使用。 这可确保与包含文件路径的状态相关的硬件存在并已做好通信准备。

删除驱动程序包

默认情况下,如果驱动程序包仍安装在任何设备上,则无法将其从系统中删除。 但是,一些用于从系统中删除驱动程序包的选项允许尝试将其“强制”删除。 即使驱动程序包仍安装在系统的某些设备上,此操作也将尝试删除该驱动程序包。 对于包含任何“从驱动程序存储运行”文件的驱动程序包,不允许将其强制删除。 从系统中删除驱动程序包时,会删除其驱动程序存储内容。 如果仍有任何设备随该驱动程序包一起安装,则该驱动程序包中的任何“从驱动程序存储运行”文件都将消失,这些丢失的文件可能会导致该设备出现故障。 为防止设备进入这样的错误状态,不能强制删除包含任何“从驱动程序存储运行”文件的驱动程序包。 只有当它们不再安装在任何设备上时,才能将其删除。 为帮助删除此类驱动程序包,可以使用 DiUninstallDriverpnputil /delete-driver <oem#.inf> /uninstall。 在尝试删除驱动程序包之前,这些删除方法将首先更新使用要删除的驱动程序包的任何设备,使其不再随该驱动程序包一起安装。

驱动程序包开发

测试专用二进制文件

开发驱动程序包时,如果需要将驱动程序包中的特定可执行文件替换为专用版本,而不是在系统上完全重新生成和替换该驱动程序包,则建议将内核调试器与 .kdfiles 命令结合使用。 由于不应对驱动程序存储中文件的完整路径进行硬编码,因此建议在 .kdfiles 映射中,OldDriver 文件名只是文件的直接名称,不包含前面所述的路径信息。 为促进这一点(和其他方案),驱动程序包中的文件名应尽可能唯一,以免与系统上无关驱动程序包中的文件名匹配。

移植 INF 以使用“从驱动程序存储运行”

如果你有一个现有的驱动程序包,其 INF 不使用“从驱动程序存储运行”,该包正在移植 INF 以使用“从驱动程序存储运行”,以下示例显示了 INF 中的一些常见文件用法以及更新这些文件以从 DriverStore 运行的模式。

服务二进制文件

如果 INF 添加了服务并且二进制文件未从驱动程序存储运行,则 INF 可能如下所示:

[DestinationDirs]
 ; Copy the file to %windir%\system32\drivers
 Example_CopyFiles = 12

[ExampleDDInstall]
CopyFiles = Example_CopyFiles

[Example_CopyFiles]
ExampleBinary.sys

[ExampleDDInstall.Services]
AddService = ExampleService,0x2,Example_Service_Inst

[Example_Service_Inst]
DisplayName   = %SvcDesc%
ServiceType   = %SERVICE_KERNEL_DRIVER%
StartType     = %SERVICE_DEMAND_START%
ErrorControl  = %SERVICE_ERROR_NORMAL%
; Point at the file in %windir%\system32\drivers
ServiceBinary = %12%\ExampleBinary.sys

若要移动此文件以从从驱动程序存储运行,将需要更新 DestinationDirs 条目以了解文件将被复制到的位置,并更新引用此文件位置的 ServiceBinary 指令。

[DestinationDirs]
; Update the destination to DIRID 13
Example_CopyFiles = 13

[ExampleDDInstall]
CopyFiles = Example_CopyFiles

[Example_CopyFiles]
ExampleBinary.sys

[ExampleDDInstall.Services]
AddService = ExampleService,0x2,Example_Service_Inst

[Example_Service_Inst]
DisplayName   = %SvcDesc%
ServiceType   = %SERVICE_KERNEL_DRIVER%
StartType     = %SERVICE_DEMAND_START%
ErrorControl  = %SERVICE_ERROR_NORMAL%
; Point at the run from Driver Store file using DIRID 13
ServiceBinary = %13%\ExampleBinary.sys

UMDF 驱动程序二进制文件

如果 INF 添加了 UMDF 驱动程序并且二进制文件未从驱动程序存储运行,则 INF 可能如下所示:

[DestinationDirs]
; Copy the file to %windir%\system32\drivers\UMDF
Example_CopyFiles = 12, UMDF

[ExampleDDInstall]
CopyFiles = Example_CopyFiles

[Example_CopyFiles]
ExampleUmdfDriver.dll

[ExampleDDInstall.Wdf]
UmdfService = ExampleUmdfDriver,Example_UMDF_Inst
...

[Example_UMDF_Inst]
; Point at the file in %windir%\system32\drivers\UMDF
ServiceBinary = %12%\UMDF\ExampleUmdfDriver.dll
...

若要移动此文件以从从驱动程序存储运行,将需要更新 DestinationDirs 条目以了解文件将被复制到的位置,并更新引用此文件位置的 ServiceBinary 指令。

[DestinationDirs]
; Update the destination to DIRID 13
Example_CopyFiles = 13

[ExampleDDInstall]
CopyFiles = Example_CopyFiles

[Example_CopyFiles]
ExampleUmdfDriver.dll

[ExampleDDInstall.Wdf]
UmdfService = ExampleUmdfDriver,Example_UMDF_Inst
...

[Example_UMDF_Inst]
; Point at the run from Driver Store file using DIRID 13
ServiceBinary = %13%\ExampleUmdfDriver.dll
...

其他文件

如果 INF 添加了一个可能由其他组件加载并且未从驱动程序存储运行的文件,则 INF 可能如下所示。 在此示例中,仅将文件名称写入设备的注册表状态。 读取此注册表值以确定要加载哪个文件的组件将取决于文件是否位于 %windir%\system32 中或取决于 LoadLibrary 的搜索顺序是否能够找到该文件。

[DestinationDirs]
; Copy the file to %windir%\system32
Example_CopyFiles = 11

[ExampleDDInstall]
CopyFiles=Example_CopyFiles
AddReg=Example_AddReg

[Example_CopyFiles]
ExampleFile.dll

[Example_AddReg]
HKR,,FileLocation,,"ExampleFile.dll"

若要移动此文件以从驱动程序存储运行,将需要更新 DestinationDirs 条目以了解文件将被复制到的位置,并更新保存在设备状态中的位置。 这要求读取该注册表值的组件能够处理该注册表值作为文件的完整路径,而不是相对于 %windir%\system32 的文件。

[DestinationDirs]
Example_CopyFiles = 13 ; update the destination to DIRID 13

[ExampleDDInstall]
CopyFiles=Example_CopyFiles
AddReg=Example_AddReg

[Example_CopyFiles]
ExampleFile.dll

[Example_AddReg]
; Point at the run from Driver Store file using DIRID 13
HKR,,FileLocation,,"%13%\ExampleFile.dll"