如何编写第一个 USB 客户端驱动程序 (UMDF)

在本文中,你将使用 Microsoft Visual Studio 2022 随附的用户模式驱动程序、USB (UMDF V2) 模板编写用户模式驱动程序框架, (基于 UMDF) 的客户端驱动程序。 生成并安装客户端驱动程序后,你将在 设备管理器 中查看客户端驱动程序,并在调试器中查看驱动程序输出。

本文中称为框架的 UMDF () 基于 COM) (组件对象模型。 默认情况下,每个框架对象都必须实现 IUnknown 及其方法 、QueryInterfaceAddRefReleaseAddRefRelease 方法管理对象的生存期,因此客户端驱动程序不需要维护引用计数。 QueryInterface 方法使客户端驱动程序能够获取指向 Windows 驱动程序框架 (WDF) 对象模型中其他框架对象的接口指针。 框架对象执行复杂的驱动程序任务并与 Windows 交互。 某些框架对象公开使客户端驱动程序能够与框架交互的接口。

基于 UMDF 的客户端驱动程序作为进程内 COM 服务器实现 (DLL) ,C++ 是编写 USB 设备客户端驱动程序的首选语言。 通常,客户端驱动程序实现框架公开的多个接口。 本文引用了一个客户端驱动程序定义的类,该类将框架接口实现为回调类。 实例化这些类后,生成的回调对象与特定的框架对象合作。 这种合作关系使客户端驱动程序能够响应框架报告的与设备或系统相关的事件。 每当 Windows 通知框架某些事件时,框架都会调用客户端驱动程序的回调(如果可用)。 否则,框架将继续对 事件进行默认处理。 模板代码定义驱动程序、设备和队列回调类。

有关模板生成的源代码的说明,请参阅 了解 USB 客户端驱动程序的 UMDF 模板代码

准备工作

若要开发、调试和安装用户模式驱动程序,需要两台计算机:

  • 运行Windows 10或更高版本的 Windows 操作系统的主计算机。 主计算机是开发环境,可在其中编写和调试驱动程序。
  • 运行要测试驱动程序的操作系统版本的目标计算机,例如,Windows 11版本 22H2。 目标计算机具有要调试的用户模式驱动程序和其中一个调试器。

在某些情况下,如果主机和目标计算机运行同一版本的 Windows,则只能有一台计算机运行Windows 10或更高版本的 Windows。 本文假设你使用两台计算机来开发、调试和安装用户模式驱动程序。

在开始之前,请确保满足以下要求:

软件要求

  • 主计算机具有 Visual Studio 2022。

  • 主计算机具有适用于 Windows 11 版本 22H2 的最新 Windows 驱动程序工具包 (WDK) 。

    该工具包包括开发、生成和调试 USB 客户端驱动程序所需的标头、库、工具、文档和调试工具。 可以从如何获取 WDK 获取最新版本 的 WDK

  • 主计算机具有最新版本的 Windows 调试工具。 可以从 WDK 获取最新版本,也可以 下载并安装适用于 Windows 的调试工具

  • 如果使用两台计算机,则必须配置主机和目标计算机进行用户模式调试。 有关详细信息,请参阅 在 Visual Studio 中设置 User-Mode 调试

硬件要求

获取要为其编写客户端驱动程序的 USB 设备。 在大多数情况下,会为你提供 USB 设备及其硬件规格。 该规范描述了设备功能和支持的供应商命令。 使用规范确定 USB 驱动程序的功能和相关设计决策。

如果你不熟悉 USB 驱动程序开发,请使用 OSR USB FX2 学习工具包 来研究 WDK 附带的 USB 示例。 它包含 USB FX2 设备和实现客户端驱动程序所需的所有硬件规范。

步骤 1:生成驱动程序代码

有关编写 UMDF 驱动程序代码的详细信息,请参阅 基于模板编写 UMDF 驱动程序

对于特定于 USB 的代码,请在 Visual Studio 2022 中选择以下选项

  1. 在“ 新建项目 ”对话框顶部的搜索框中,键入 “USB”。
  2. 在中间窗格中,选择“ 用户模式驱动程序”、“USB (UMDF V2) ”。
  3. 选择“下一页”。
  4. 输入项目名称,选择保存位置,然后选择 “创建”。

以下屏幕截图显示了 USB User-Mode 驱动程序模板的“新建项目”对话框。

Visual Studio 创建项目选项的屏幕截图。

Visual Studio“创建项目配置”屏幕的屏幕截图。

本文假定项目的名称MyUSBDriver_UMDF_。 它包含以下文件:

文件 说明
Driver.h;Driver.c 包含驱动程序模块入口点的实现。 DriverEntry 和 WDFDRIVER 相关的功能和回调。
Device.h;Device.c WDFDEVICE 和 WDFUSBDEVICE 相关的功能和回调。
Queue.h;Queue.c WDFQUEUE 相关的功能和回调。
Trace.h 定义设备接口 GUID。 它还声明跟踪函数和宏。
<项目名称>.inf 在目标计算机上安装客户端驱动程序所需的 INF 文件。

步骤 2:添加有关设备的信息

在生成驱动程序之前,必须添加有关设备的信息,特别是硬件 ID。 提供硬件 ID:

  1. “解决方案资源管理器”窗口中,右键单击“MyUSBDriver_UMDF_”,然后选择“属性”。
  2. “MyUSBDriver_UMDF_属性页” 窗口中,转到 “配置属性驱动程序 > 安装 > 部署”,如下所示。 Visual Studio 2022 属性页窗口的屏幕截图。
  3. 选中“部署前删除以前的驱动程序版本” 。
  4. 对于“目标设备名称” ,请选择配置用于测试和调试的计算机名。
  5. 选择“硬件 ID 驱动程序更新” ,然后输入驱动程序的硬件 ID。 在本练习中,硬件 ID 为 Root\MyUSBDriver_UMDF_。 选择“确定” 。

注意

在本练习中,硬件 ID 不标识真实的硬件。 它标识了虚构设备,该设备位于设备树中,作为根节点的子节点。 对于实际硬件,请勿选择 “硬件 ID 驱动程序更新”。 改为选择“ 安装并验证”。 可以在驱动程序的信息 (INF) 文件中看到硬件 ID。 在“解决方案资源管理器”窗口中,转到“MyUSBDriver_UMDF_>驱动程序文件”,然后双击“MyUSBDriver_UMDF_.inf”。 硬件 ID 位于 [Standard.NT$ARCH$] 下。

所有基于 UMDF 的 USB 客户端驱动程序都需要 Microsoft 提供的两个驱动程序:反射器和 WinUSB。

  • 反射器:如果驱动程序成功加载,反射器将作为内核模式堆栈中的最顶层驱动程序加载。 反射器必须是内核模式堆栈中的顶级驱动程序。 为了满足此要求,模板的 INF 文件将反射器指定为服务,将 WinUSB 指定为 INF 中的低筛选器驱动程序:

    [MyDevice_Install.NT.Services]
    AddService=WUDFRd,0x000001fa,WUDFRD_ServiceInstall  ; flag 0x2 sets this as the service for the device
    AddService=WinUsb,0x000001f8,WinUsb_ServiceInstall  ; this service is installed because its a filter.
    
  • WinUSB:安装包必须包含用于 Winusb.sys 的 cointaller,因为对于客户端驱动程序,WinUSB 是内核模式 USB 驱动程序堆栈的网关。 加载的另一个组件是客户端驱动程序的主机进程 (Wudfhost.exe) 中名为 WinUsb.dll 的用户模式 DLL。 Winusb.dll 公开 WinUSB 函数 ,可简化客户端驱动程序与 WinUSB 之间的通信过程。

步骤 3:生成 USB 客户端驱动程序代码

若要生成驱动程序,请执行以下操作:

  1. 在 Visual Studio 2022 中打开驱动程序项目或解决方案。
  2. 右键单击解决方案资源管理器中的解决方案,然后选择“Configuration Manager”。
  3. Configuration Manager,选择活动解决方案配置 (例如“调试”或“发布) ”和“活动解决方案平台” (,例如,对应于感兴趣的生成类型的 x64) 。
  4. 验证设备接口 GUID 在整个项目中是否准确。
    • 设备接口 GUID 在 Trace.h 中定义,并从 MyUSBDriverUMDFCreateDevice Device.c 中引用。 创建名称 为 MyUSBDriver_UMDF_ 的项目时,Visual Studio 2022 使用名称 GUID_DEVINTERFACE_MyUSBDriver_UMDF_ 定义设备接口 GUID,但调用 WdfDeviceCreateDeviceInterface 的参数不正确 &GUID_DEVINTERFACE_MyUSBDriverUMDF。 将不正确的参数替换为 Trace.h 中定义的名称,以确保驱动程序正确生成。
  5. 从“构建”菜单中,选择“构建解决方案”。

有关详细信息,请参阅构建驱动程序

步骤 4:配置计算机以进行测试和调试

若要测试和调试驱动程序,请在主计算机上运行调试器,并在目标计算机上运行驱动程序。 到目前为止,已在主计算机上使用 Visual Studio 来生成驱动程序。 接下来,需要配置目标计算机。 若要配置目标计算机,请按照 预配计算机进行驱动程序部署和测试中的说明进行操作。

步骤 5:为内核调试启用跟踪

模板代码包含多个跟踪消息 (TraceEvents) ,可帮助你跟踪函数调用。 源代码中的所有函数都包含标记例程的进入和退出的跟踪消息。 对于错误,跟踪消息包含错误代码和有意义的字符串。 由于为驱动程序项目启用了 WPP 跟踪,因此在生成过程中创建的 PDB 符号文件包含跟踪消息格式设置说明。 如果为 WPP 跟踪配置主机和目标计算机,驱动程序可以将跟踪消息发送到文件或调试器。

为 WPP 跟踪配置主计算机

  1. 通过从 PDB 符号文件提取跟踪消息格式设置说明, (TMF) 文件创建跟踪消息格式。

    可以使用 Tracepdb.exe 创建 TMF 文件。 该工具位于 WDK 的 <安装文件夹>Windows Kits\10\bin\<architecture> 文件夹中。 以下命令为驱动程序项目创建 TMF 文件。

    tracepdb -f <PDBFiles> -p <TMFDirectory>
    

    -f 选项指定 PDB 符号文件的位置和名称。 -p 选项指定 Tracepdb 创建的 TMF 文件的位置。 有关详细信息,请参阅 Tracepdb 命令

    指定位置有三个文件,项目中每个 C 代码文件一个。 它们获得 GUID 文件名。

  2. 在调试器中,键入以下命令:

    .load Wmitrace
    .chain
    !wmitrace.searchpath + <TMF file location>
    

这些命令会:

  • 加载 Wmitrace.dll 扩展。
  • 验证是否已加载调试器扩展。
  • 将 TMF 文件的位置添加到调试器扩展的搜索路径。

输出如下所示:

Trace Format search path is: 'C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE;c:\drivers\tmf

为 WPP 跟踪配置目标计算机

  1. 请确保目标计算机上具有 Tracelog 工具。 该工具位于 <WDK install_folder>Windows Kits\10\Tools\<arch> 文件夹中。 有关详细信息,请参阅 Tracelog 命令语法

  2. 打开 命令窗口 并以管理员身份运行。

  3. 输入以下命令:

    tracelog -start MyTrace -guid \#c918ee71-68c7-4140-8f7d-c907abbcb05d -flag 0xFFFF -level 7-rt -kd
    

命令启动名为 MyTrace 的跟踪会话。

guid 参数指定跟踪提供程序(即客户端驱动程序)的 GUID。 可以从 Visual Studio 2022 项目中的 Trace.h 获取 GUID。 另一个选项是,可以键入以下命令并在 .guid 文件中指定 GUID。 文件包含连字符格式的 GUID:

tracelog -start MyTrace -guid c:\\drivers\\Provider.guid -flag 0xFFFF -level 7-rt -kd

可以通过键入以下命令来停止跟踪会话:

tracelog -stop MyTrace

步骤 6:在目标计算机上部署驱动程序

  1. 解决方案资源管理器窗口中,右键单击项目名称 (MyUSBDriver_UMDF_) ,然后选择“属性”。
  2. 在左窗格中,导航到 “配置属性驱动程序 > 安装 > 部署”。
  3. 对于 “目标设备名称”,指定目标计算机的名称。
  4. 选择 “安装/重新安装并验证”。
  5. 选择“确定” 。
  6. 在“调试” 菜单上,选择“开始调试” 或按键盘上的 F5

注意

不要在“硬件 ID 驱动程序更新”下指定设备的硬件 ID。 只能在驱动程序的信息中指定硬件 ID, (INF) 文件。

步骤 7:在 设备管理器 中查看驱动程序

  1. 输入以下命令以打开设备管理器

    devmgmt
    
  2. 验证设备管理器是否显示以下节点。

    USB 设备

    MyUSBDriver_UMDF_Device

步骤 8:在调试器中查看输出

验证跟踪消息是否显示在主计算机上的 调试器即时窗口中

输出应如下所示:

[0]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::OnPrepareHardware Entry
[0]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::OnPrepareHardware Exit
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::CreateInstanceAndInitialize Entry
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::Initialize Entry
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::Initialize Exit
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::CreateInstanceAndInitialize Exit
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::Configure Entry
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyIoQueue::CreateInstanceAndInitialize Entry
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyIoQueue::Initialize Entry
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyIoQueue::Initialize Exit
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyIoQueue::CreateInstanceAndInitialize Exit
[1]0744.05F0::00/00/0000-00:00:00.000 [MyUSBDriver_UMDF_]CMyDevice::Configure Exit

注解

让我们看看框架和客户端驱动程序如何协同工作来与 Windows 交互并处理发送到 USB 设备的请求。 此图显示了系统中为基于 UMDF 的 USB 客户端驱动程序加载的模块。

用户模式客户端驱动程序体系结构的关系图。

下面介绍了每个模块的用途:

  • 应用程序 - 发出 I/O 请求以与 USB 设备通信的用户模式进程。
  • I/O 管理器 - 一个 Windows 组件,用于创建 I/O 请求数据包 (IRP) 表示接收的应用程序请求,并将其转发到目标设备的内核模式设备堆栈的顶部。
  • 反射器 - Microsoft 提供的内核模式驱动程序安装在内核模式设备堆栈的顶部 (WUDFRd.sys) 。 反射器将从 I/O 管理器收到的 IRP 重定向到客户端驱动程序主机进程。 收到请求后,框架和客户端驱动程序将处理请求。
  • 主机进程 — 用户模式驱动程序运行的进程 (Wudfhost.exe) 。 它还托管框架和 I/O 调度程序。
  • 客户端驱动程序 - USB 设备的用户模式函数驱动程序。
  • UMDF - 代表客户端驱动程序处理与 Windows 的大多数交互的框架模块。 它公开用户模式设备驱动程序接口 (DDI) 客户端驱动程序可用于执行常见驱动程序任务。
  • 调度程序 - 在主机进程中运行的机制;确定请求在用户模式驱动程序处理并到达用户模式堆栈的底部后,如何将请求转发到内核模式。 在图中,调度程序将请求转发到用户模式 DLL,Winusb.dll。
  • Winusb.dll - Microsoft 提供的用户模式 DLL,用于公开 WinUSB Functions ,可简化客户端驱动程序与 WinUSB (Winusb.sys 之间的通信过程,) 加载内核模式。
  • Winusb.sys — Microsoft 提供的驱动程序,是 USB 设备的所有 UMDF 客户端驱动程序所需的驱动程序。 驱动程序必须安装在反射器下方,并在内核模式下充当 USB 驱动程序堆栈的网关。 有关详细信息,请参阅 WinUSB
  • USB 驱动程序堆栈 - Microsoft 提供的一组驱动程序,用于处理与 USB 设备的协议级通信。 有关详细信息,请参阅 Windows 中的 USB 主机端驱动程序

每当应用程序对 USB 驱动程序堆栈发出请求时,Windows I/O 管理器会将请求发送到反射器,反射器会将请求定向到用户模式下的客户端驱动程序。 客户端驱动程序通过调用特定的 UMDF 方法来处理请求,这些方法在内部调用 WinUSB Functions 以将请求发送到 WinUSB。 收到请求后,WinUSB 会处理请求或将其转发到 USB 驱动程序堆栈。