创建导出驱动程序

导出驱动程序是内核模式 DLL,可由各种其他特定于硬件或特定于设备堆栈的组件加载,但不具有完整内核模式驱动程序的某些特征。 具体而言,导出驱动程序没有调度表,它在 驱动程序堆栈中没有位置,并且它在服务控制管理器的数据库中没有将其定义为系统服务的条目。 虽然导出驱动程序没有 调度表,但它可以为标准驱动程序提供 调度例程 。 标准驱动程序将调度例程插入其自己的调度表中。 导出驱动程序具有从不调用的存根 DriverEntry 例程。

内核模式导出驱动程序特别适用于实现独立于基础堆栈和硬件特征的驱动程序对的部分。

Windows 包含多个由其他驱动程序加载的导出驱动程序,例如:

  • SCSI 端口驱动程序
  • 磁带类驱动程序
  • IDE 控制器驱动程序都是系统提供的导出驱动程序

标准驱动程序还可以充当导出驱动程序。 要使驱动程序以这两种方式运行,必须将其生成为导出驱动程序,并作为常规驱动程序加载。

生成导出驱动程序

若要在 Visual Studio 中创建导出驱动程序,请使用以下过程:

  1. 从模板创建新项目,例如 空 WDM 驱动程序
  2. 将模块定义文件添加到项目,例如:
LIBRARY mydriver.sys
EXPORTS
  DllInitialize PRIVATE
  DllUnload PRIVATE

内核模式 DLL 的入口点始终为 DllInitialize。 加载 DLL 后,系统会立即调用内核模式 DLL 的 DllInitialize 例程。 导出驱动程序必须提供 DllInitialize 例程。 可以使用 DllInitialize 例程获取或初始化 DLL 中的其他例程所需的资源。

不能使用 DLLENTRY 宏指定入口点。

NTSTATUS DllInitialize(
  _In_ PUNICODE_STRING RegistryPath
);

RegistryPath 是指向计数 Unicode 字符串的指针,该字符串指定 DLL 注册表项的路径, HKEY_LOCAL_MACHINE\CurrentControlSet\Services\DllName。 DLL 例程可以使用此密钥来存储 DLL 特定的信息。 DllInitialize 退出后,将释放 RegistryPath 指向的缓冲区。 因此,如果 DLL 使用密钥, DllInitialize 必须复制密钥名称。

生成过程生成具有 .lib 扩展名的导出库,以及具有 .sys 扩展名的导出驱动程序。

从导出驱动程序导入函数

若要导入由导出驱动程序导出的函数,应使用 ntdef.h 中定义的 DECLSPEC_IMPORT 宏声明这些函数。 例如:

DECLSPEC_IMPORT int LoadPrinterDriver (int arg1); 

此宏解析为 __declspec (dllimport) 这些平台上的存储类声明(如果需要),在不需要的平台上则不执行任何声明。

在导出驱动程序中,应使用 DECLSPEC_EXPORT 宏声明要导出的函数。 此宏解析为 __declspec (dllexport) 这些平台上的存储类声明(如果需要),在不需要的平台上则不执行任何声明。 如果导出驱动程序向标准驱动程序提供调度例程,则无需导出该例程。

加载和卸载导出驱动程序

导出驱动程序必须安装在 %Windir%\System32\Drivers 目录中。 从 Windows 2000 开始,操作系统会保留一个引用计数,指示其他驱动程序导入导出驱动程序函数的次数。 每当其中一个导入驱动程序卸载时,系统会减少此计数。 当引用计数下降到零时,系统会卸载导出驱动程序。 但是,导出驱动程序必须包含标准入口点和卸载例程 DllInitializeDllUnload,否则操作系统不会激活此引用计数机制。

系统在卸载 DLL 时调用内核模式 DLL 的 DllUnload 例程。

NTSTATUS DllUnload(void);

导出驱动程序必须提供 DllUnload 例程。 可以使用 DllUnload 例程释放 DLL 中的例程使用的任何资源。