了解 USB 客户端驱动程序代码结构 (UMDF)

在本主题中,你将了解基于 UMDF 的 USB 客户端驱动程序的源代码。 代码示例由 Microsoft Visual Studio 附带的 USB 用户模式驱动程序 模板生成。 模板代码使用活动模板库 (ATL) 生成 COM 基础结构。 此处不讨论有关客户端驱动程序中的 COM 实现的 ATL 和详细信息。

有关生成 UMDF 模板代码的说明,请参阅 如何 (UMDF) 编写第一个 USB 客户端驱动程序 。 以下部分讨论了模板代码:

在讨论模板代码的详细信息之前,让我们看看头文件中与 UMDF 驱动程序开发相关的一些声明 (Internal.h) 。

Internal.h 包含以下文件,包含在 Windows 驱动程序工具包 (WDK) :

#include "atlbase.h"
#include "atlcom.h"

#include "wudfddi.h"
#include "wudfusb.h"

Atlbase.h 和 atlcom.h 包括 ATL 支持的声明。 客户端驱动程序实现的每个类都实现 ATL 类公共 CComObjectRootEx。

Wudfddi.h 始终包含在 UMDF 驱动程序开发中。 头文件包含编译 UMDF 驱动程序所需的方法和结构的各种声明和定义。

Wudfusb.h 包括与框架提供的 USB I/O 目标对象进行通信所需的 UMDF 结构和方法的声明和定义。

Internal.h 中的下一个块声明设备接口的 GUID 常量。 应用程序可以使用此 GUID 通过 SetupDiXxx API 打开设备的句柄。 在框架创建设备对象后注册 GUID。

// Device Interface GUID
// f74570e5-ed0c-4230-a7a5-a56264465548

DEFINE_GUID(GUID_DEVINTERFACE_MyUSBDriver_UMDF_,
    0xf74570e5,0xed0c,0x4230,0xa7,0xa5,0xa5,0x62,0x64,0x46,0x55,0x48);

下一部分声明跟踪宏和跟踪 GUID。 请注意跟踪 GUID;你将需要它才能启用跟踪。

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        MyDriver1TraceGuid, (f0261b19,c295,4a92,aa8e,c6316c82cdf0),    \
                                                                       \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)                              \
        WPP_DEFINE_BIT(TRACE_DRIVER)                                   \
        WPP_DEFINE_BIT(TRACE_DEVICE)                                   \
        WPP_DEFINE_BIT(TRACE_QUEUE)                                    \
        )                             

#define WPP_FLAG_LEVEL_LOGGER(flag, level)                             \
    WPP_LEVEL_LOGGER(flag)

#define WPP_FLAG_LEVEL_ENABLED(flag, level)                            \
    (WPP_LEVEL_ENABLED(flag) &&                                        \
     WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)

#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
           WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
           (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

Internal.h forward 中的下一行声明队列回调对象的客户端驱动程序实现的类。 它还包括模板生成的其他项目文件。 本主题稍后将讨论实现文件和项目头文件。

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

#include "Driver.h"
#include "Device.h"
#include "IoQueue.h"

安装客户端驱动程序后,Windows 会在主机进程的实例中加载客户端驱动程序和框架。 从此处,框架加载并初始化客户端驱动程序。 框架执行以下任务:

  1. 在框架中创建一个 驱动程序对象 ,该对象表示客户端驱动程序。
  2. 从类工厂请求 IDriverEntry 接口指针。
  3. 在框架中创建 设备对象
  4. 在 PnP 管理器启动设备后初始化设备对象。

在驱动程序加载和初始化时,会发生多个事件,并且框架允许客户端驱动程序参与处理这些事件。 在客户端驱动程序端,驱动程序执行以下任务:

  1. 实现并导出客户端驱动程序模块中的 DllGetClassObject 函数,以便框架可以获取对驱动程序的引用。
  2. 提供实现 IDriverEntry 接口的回调类。
  3. 提供实现 IPnpCallbackXxx 接口的回调类。
  4. 获取对设备对象的引用,并根据客户端驱动程序的要求对其进行配置。

驱动程序回调源代码

框架创建 驱动程序对象,该对象表示 Windows 加载的客户端驱动程序的实例。 客户端驱动程序提供至少一个驱动程序回调,用于向框架注册驱动程序。

驱动程序回调的完整源代码位于 Driver.h 和 Driver.c 中。

客户端驱动程序必须定义实现 IUnknownIDriverEntry 接口的驱动程序回调类。 头文件 Driver.h 声明一个名为 CMyDriver 的类,该类定义驱动程序回调。

EXTERN_C const CLSID CLSID_Driver;

class CMyDriver :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CMyDriver, &CLSID_Driver>,
    public IDriverEntry
{
public:

    CMyDriver()
    {
    }

    DECLARE_NO_REGISTRY()

    DECLARE_NOT_AGGREGATABLE(CMyDriver)

    BEGIN_COM_MAP(CMyDriver)
        COM_INTERFACE_ENTRY(IDriverEntry)
    END_COM_MAP()

public:

    // IDriverEntry methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnInitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return S_OK;
    }

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnDeviceAdd(
        __in IWDFDriver *FxWdfDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeinitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return;
    }

};

OBJECT_ENTRY_AUTO(CLSID_Driver, CMyDriver)

驱动程序回调必须是 COM 类,这意味着它必须实现 IUnknown 和相关方法。 在模板代码中,ATL 类 CComObjectRootEx 和 CComCoClass 包含 IUnknown 方法。

Windows 实例化主机进程后,框架将创建驱动程序对象。 为此,框架创建驱动程序回调类的实例,并调用驱动程序实现 DllGetClassObject () 驱动程序 条目源代码 部分中讨论,并获取客户端驱动程序的 IDriverEntry 接口指针。 该调用将驱动程序回调对象注册到框架驱动程序对象。 成功注册后,当发生某些特定于驱动程序的事件时,框架将调用客户端驱动程序的实现。 框架调用的第一个方法是 IDriverEntry::OnInitialize 方法。 在客户端驱动程序的 IDriverEntry::OnInitialize 实现中,客户端驱动程序可以分配全局驱动程序资源。 在准备卸载客户端驱动程序之前,必须在框架调用的 IDriverEntry::OnDeinitialize 中发布这些资源。 模板代码为 OnInitializeOnDeinitialize 方法提供最小实现。

IDriverEntry 最重要的方法是 IDriverEntry::OnDeviceAdd。 在框架创建框架设备对象 () 下一节中讨论之前,它会调用驱动程序的 IDriverEntry::OnDeviceAdd 实现。 调用 方法时,框架将 IWDFDriver 指针传递给驱动程序对象,并将 IWDFDeviceInitialize 指针传递给驱动程序对象。 客户端驱动程序可以调用 IWDFDeviceInitialize 方法来指定某些配置选项。

通常,客户端驱动程序在其 IDriverEntry::OnDeviceAdd 实现中执行以下任务:

  • 指定要创建的设备对象的配置信息。
  • 实例化驱动程序的设备回调类。
  • 创建框架设备对象,并将其设备回调对象注册到框架。
  • 初始化框架设备对象。
  • 注册客户端驱动程序的设备接口 GUID。

在模板代码中, IDriverEntry::OnDeviceAdd 调用在设备回调类中定义的静态方法 CMyDevice::CreateInstanceAndInitialize。 静态方法首先实例化客户端驱动程序的设备回调类,然后创建框架设备对象。 设备回调类还定义了一个名为 Configure 的公共方法,该方法执行前面列表中提到的剩余任务。 下一部分将讨论设备回调类的实现。 下面的代码示例演示模板代码中的 IDriverEntry::OnDeviceAdd 实现。

HRESULT
CMyDriver::OnDeviceAdd(
    __in IWDFDriver *FxWdfDriver,
    __in IWDFDeviceInitialize *FxDeviceInit
    )
{
    HRESULT hr = S_OK;
    CMyDevice *device = NULL;

    hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
                                                FxDeviceInit,
                                                &device);

    if (SUCCEEDED(hr))
    {
        hr = device->Configure();
    }

    return hr;
}

下面的代码示例演示 Device.h 中的设备类声明。

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:

    IWDFDevice *            m_FxDevice;

    CMyIoQueue *            m_IoQueue;

    IWDFUsbTargetDevice *   m_FxUsbDevice;

private:

    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:

    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );
public:

    // IPnpCallbackHardware methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

设备回调源代码

框架设备对象是表示在客户端驱动程序的设备堆栈中加载的设备对象的框架类的实例。 有关设备对象功能的信息,请参阅 设备节点和设备堆栈

设备对象的完整源代码位于 Device.h 和 Device.c 中。

框架设备类实现 IWDFDevice 接口。 客户端驱动程序负责在 IDriverEntry::OnDeviceAdd 的驱动程序实现中创建该类的实例。 创建对象后,客户端驱动程序获取指向新对象的 IWDFDevice 指针,并在该接口上调用方法来管理设备对象的操作。

IDriverEntry::OnDeviceAdd 实现

在上一部分中,你简要了解了客户端驱动程序在 IDriverEntry::OnDeviceAdd 中执行的任务。 下面是有关这些任务的详细信息。 客户端驱动程序:

  • 指定要创建的设备对象的配置信息。

    在对 IDriverEntry::OnDeviceAdd 方法的客户端驱动程序实现的框架调用中,框架传递 IWDFDeviceInitialize 指针。 客户端驱动程序使用此指针指定要创建的设备对象的配置信息。 例如,客户端驱动程序指定客户端驱动程序是筛选器还是函数驱动程序。 若要将客户端驱动程序标识为筛选器驱动程序,请调用 IWDFDeviceInitialize::SetFilter。 在这种情况下,框架会 (FiDO) 创建筛选器设备对象;否则,将创建 FDO) (函数设备对象。 另一个可以设置的选项是通过调用 IWDFDeviceInitialize::SetLockingConstraint 来设置同步模式。

  • 通过传递 IWDFDeviceInitialize 接口指针、设备回调对象的 IUnknown 引用和指向指针的 IWDFDevice 变量来调用 IWDFDriver::CreateDevice 方法。

    如果 IWDFDriver::CreateDevice 调用成功:

    • 框架创建设备对象。

    • 框架向框架注册设备回调。

      将设备回调与框架设备对象配对后,框架和客户端驱动程序将处理某些事件,例如 PnP 状态和电源状态更改。 例如,当 PnP 管理器启动设备时,框架会收到通知。 然后,框架调用设备回调的 IPnpCallbackHardware::OnPrepareHardware 实现。 每个客户端驱动程序必须至少注册一个设备回调对象。

    • 客户端驱动程序在 IWDFDevice 变量中接收新设备对象的地址。 收到指向框架设备对象的指针后,客户端驱动程序可以继续执行初始化任务,例如设置 I/O 流的队列和注册设备接口 GUID。

  • 调用 IWDFDevice::CreateDeviceInterface 来注册客户端驱动程序的设备接口 GUID。 应用程序可以使用 GUID 将请求发送到客户端驱动程序。 GUID 常量在 Internal.h 中声明。

  • 初始化用于传入和传出设备的 I/O 传输的队列。

模板代码定义帮助程序方法 Initialize,该方法指定配置信息并创建设备对象。

下面的代码示例演示 Initialize 的实现。

HRESULT
CMyDevice::Initialize(
    __in IWDFDriver           * FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    )
{
    IWDFDevice *fxDevice = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    FxDeviceInit->SetLockingConstraint(None);

    FxDeviceInit->SetPowerPolicyOwnership(TRUE);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get IUnknown %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
    DriverSafeRelease(unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create a framework device %!hresult!",
                    hr);
        goto Exit;
    }

     m_FxDevice = fxDevice;

     DriverSafeRelease(fxDevice);

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

在前面的代码示例中,客户端驱动程序创建设备对象并注册其设备回调。 在创建设备对象之前,驱动程序通过调用 IWDFDeviceInitialize 接口指针上的方法来指定其配置首选项。 这是框架在上一次调用客户端驱动程序的 IDriverEntry::OnDeviceAdd 方法时传递的指针。

客户端驱动程序指定它将是设备对象的电源策略所有者。 作为电源策略所有者,客户端驱动程序确定在系统电源状态更改时设备应进入的相应电源状态。 驱动程序还负责向设备发送相关请求,以便进行电源状态转换。 默认情况下,基于 UMDF 的客户端驱动程序不是电源策略所有者;框架处理所有电源状态转换。 当系统进入睡眠状态时,框架会自动将设备发送到 D3 ,相反,当系统进入 S0 工作状态时,框架会将设备返回到 D0。 有关详细信息,请参阅 UMDF 中的 Power Policy 所有权

另一个配置选项是指定客户端驱动程序是设备的筛选器驱动程序还是函数驱动程序。 请注意,在代码示例中,客户端驱动程序未显式指定其首选项。 这意味着客户端驱动程序是函数驱动程序,框架应在设备堆栈中创建 FDO。 如果客户端驱动程序希望成为筛选器驱动程序,则驱动程序必须调用 IWDFDeviceInitialize::SetFilter 方法。 在这种情况下,框架会在设备堆栈中创建 FiDO。

客户端驱动程序还指定不同步框架对客户端驱动程序回调的调用。 客户端驱动程序处理所有同步任务。 若要指定该首选项,客户端驱动程序调用 IWDFDeviceInitialize::SetLockingConstraint 方法。

接下来,客户端驱动程序通过调用 IUnknown ::QueryInterface 获取指向其设备回调类 的 IUnknown 指针。 随后,客户端驱动程序调用 IWDFDriver::CreateDevice,后者使用 IUnknown 指针创建框架设备对象并注册客户端驱动程序的设备回调。

请注意,客户端驱动程序将 (通过 IWDFDriver::CreateDevice 调用) 收到的设备对象的地址存储在设备回调类的专用数据成员中,然后通过调用 Internal.h) 中定义的 DriverSafeRelease (内联函数释放该引用。 这是因为设备对象的生存期由框架跟踪。 因此,客户端驱动程序不需要保留设备对象的其他引用计数。

模板代码定义公共方法 Configure,该方法注册设备接口 GUID 并设置队列。 下面的代码示例演示设备回调类 CMyDevice 中 Configure 方法的定义。 创建框架设备对象后, IDriverEntry::OnDeviceAdd 调用配置。

CMyDevice::Configure(
    VOID
    )
{

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

     hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create and initialize queue %!hresult!",
                    hr);
        goto Exit;
    }

    hr = m_IoQueue->Configure();
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to configure queue %!hresult!",
                    hr);
        goto Exit;
    } 

    hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_MyUSBDriver_UMDF_,NULL);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create device interface %!hresult!",
                    hr);
        goto Exit;
    }

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

在前面的代码示例中,客户端驱动程序执行两个main任务:初始化 I/O 流的队列和注册设备接口 GUID。

队列是在 CMyIoQueue 类中创建和配置的。 第一个任务是通过调用名为 CreateInstanceAndInitialize 的静态方法实例化该类。 客户端驱动程序调用 Configure 来初始化队列。 CreateInstanceAndInitialize 和 Configure 在 CMyIoQueue 中声明,本主题稍后将对此进行讨论。

客户端驱动程序还调用 IWDFDevice::CreateDeviceInterface 来注册客户端驱动程序的设备接口 GUID。 应用程序可以使用 GUID 将请求发送到客户端驱动程序。 GUID 常量在 Internal.h 中声明。

IPnpCallbackHardware 实现和特定于 USB 的任务

接下来,让我们看看 Device.cpp 中 IPnpCallbackHardware 接口的实现。

每个设备回调类都必须实现 IPnpCallbackHardware 接口。 此接口有两种方法: IPnpCallbackHardware::OnPrepareHardwareIPnpCallbackHardware::OnReleaseHardware。 框架调用这些方法以响应两个事件:PnP 管理器启动设备时和删除设备时。 设备启动时,与硬件建立通信,但设备尚未进入工作状态 (D0) 。 因此,在 IPnpCallbackHardware::OnPrepareHardware 中,客户端驱动程序可以从硬件获取设备信息、分配资源以及初始化驱动程序生存期内所需的框架对象。 当 PnP 管理器删除设备时,将从系统卸载驱动程序。 框架调用客户端驱动程序的 IPnpCallbackHardware::OnReleaseHardware 实现,其中驱动程序可以释放这些资源和框架对象。

PnP 管理器可以生成 PnP 状态更改导致的其他类型的事件。 框架为这些事件提供默认处理。 客户端驱动程序可以选择参与这些事件的处理。 假设 USB 设备与主机分离。 PnP 管理器识别该事件并通知框架。 如果客户端驱动程序要执行其他任务以响应事件,则驱动程序必须在设备回调类中实现 IPnpCallback 接口和相关 IPnpCallback::OnSurpriseRemoval 方法。 否则,框架将继续执行其事件的默认处理。

USB 客户端驱动程序必须检索有关支持的接口、备用设置和终结点的信息,并在发送数据传输的任何 I/O 请求之前对其进行配置。 UMDF 提供专用的 I/O 目标对象,可简化客户端驱动程序的许多配置任务。 若要配置 USB 设备,客户端驱动程序需要仅在 PnP 管理器启动设备后才可用的设备信息。

此模板代码在 IPnpCallbackHardware::OnPrepareHardware 方法中创建这些对象。

通常,客户端驱动程序 (执行以下一个或多个配置任务,具体取决于设备) 的设计:

  1. 检索有关当前配置的信息,例如接口数。 框架选择 USB 设备上的第一个配置。 对于多配置设备,客户端驱动程序无法选择其他配置。
  2. 检索有关接口的信息,例如终结点数。
  3. 如果接口支持多个设置,则更改每个接口中的备用设置。 默认情况下,框架在 USB 设备上的第一个配置中选择每个接口的第一个备用设置。 客户端驱动程序可以选择备用设置。
  4. 检索有关每个接口中的终结点的信息。

若要执行这些任务,客户端驱动程序可以使用 WDF 提供的这些类型的专用 USB I/O 目标对象。

USB I/O 目标对象 说明 UMDF 接口
目标设备对象 表示 USB 设备,并提供检索设备描述符和向设备发送控制请求的方法。 IWDFUsbTargetDevice
目标接口对象 表示单个接口,并提供客户端驱动程序可以调用以选择备用设置和检索有关设置的信息的方法。 IWDFUsbInterface
目标管道对象 表示在接口的当前备用设置中配置的终结点的单个管道。 USB 总线驱动程序选择所选配置中的每个接口,并设置与接口内每个终结点的信道。 在 USB 术语中,该信道称为 管道 IWDFUsbTargetPipe

下面的代码示例演示 IPnpCallbackHardware::OnPrepareHardware 的实现。

HRESULT
CMyDevice::OnPrepareHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    HRESULT hr;
    IWDFUsbTargetFactory *usbFactory = NULL;
    IWDFUsbTargetDevice *usbDevice = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&usbFactory));

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get USB target factory %!hresult!",
                    hr);
        goto Exit;
    }

    hr = usbFactory->CreateUsbTargetDevice(&usbDevice);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create USB target device %!hresult!",
                    hr);

        goto Exit;
    }

     m_FxUsbDevice = usbDevice;

Exit:

    DriverSafeRelease(usbDevice);

    DriverSafeRelease(usbFactory);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

若要使用框架的 USB I/O 目标对象,客户端驱动程序必须首先创建 USB 目标设备对象。 在框架对象模型中,USB 目标设备对象是表示 USB 设备的设备对象的子级。 USB 目标设备对象由框架实现,并执行 USB 设备的所有设备级任务,例如选择配置。

在前面的代码示例中,客户端驱动程序查询框架设备对象,并获取指向创建 USB 目标设备对象的类工厂的 IWDFUsbTargetFactory 指针。 通过使用该指针,客户端驱动程序调用 IWDFUsbTargetDevice::CreateUsbTargetDevice 方法。 方法创建 USB 目标设备对象,并返回指向 IWDFUsbTargetDevice 接口的指针。 方法还会选择默认 (第一个) 配置,并为该配置中的每个接口选择备用设置 0。

模板代码将通过 IWDFDriver::CreateDevice 调用 () 接收的 USB 目标设备对象的地址存储在设备回调类的专用数据成员中,然后通过调用 DriverSafeRelease 释放该引用。 USB 目标设备对象的引用计数由框架维护。 只要设备对象处于活动状态,对象就处于活动状态。 客户端驱动程序必须释放 IPnpCallbackHardware::OnReleaseHardware 中的引用。

客户端驱动程序创建 USB 目标设备对象后,驱动程序会调用 IWDFUsbTargetDevice 方法来执行以下任务:

  • 检索设备、配置、接口描述符和其他信息,例如设备速度。
  • 格式化 I/O 控制请求并将其发送到默认终结点。
  • 设置整个 USB 设备的电源策略。

有关详细信息,请参阅 在 UMDF 中使用 USB 设备。 下面的代码示例演示 IPnpCallbackHardware::OnReleaseHardware 的实现。

HRESULT
CMyDevice::OnReleaseHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    if (m_FxUsbDevice != NULL) {

        m_FxUsbDevice->DeleteWdfObject();
        m_FxUsbDevice = NULL;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return S_OK;
}

队列源代码

框架队列对象表示特定框架设备对象的 I/O 队列。 队列对象的完整源代码位于 IoQueue.h 和 IoQueue.c 中。

IoQueue.h

头文件 IoQueue.h 声明队列回调类。

class CMyIoQueue :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IQueueCallbackDeviceIoControl
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyIoQueue)

    BEGIN_COM_MAP(CMyIoQueue)
        COM_INTERFACE_ENTRY(IQueueCallbackDeviceIoControl)
    END_COM_MAP()

    CMyIoQueue() : 
        m_FxQueue(NULL),
        m_Device(NULL)
    {
    }

    ~CMyIoQueue()
    {
        // empty
    }

    HRESULT
    Initialize(
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice
        );

    static 
    HRESULT 
    CreateInstanceAndInitialize( 
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice,
        __out CMyIoQueue**    Queue
        );

    HRESULT
    Configure(
        VOID
        )
    {
        return S_OK;
    }


    // IQueueCallbackDeviceIoControl

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeviceIoControl( 
        __in IWDFIoQueue *pWdfQueue,
        __in IWDFIoRequest *pWdfRequest,
        __in ULONG ControlCode,
        __in SIZE_T InputBufferSizeInBytes,
        __in SIZE_T OutputBufferSizeInBytes
        );

private:

    IWDFIoQueue *               m_FxQueue;

    CMyDevice *                 m_Device;

};

在前面的代码示例中,客户端驱动程序声明队列回调类。 实例化后,对象与框架队列对象合作,该对象处理将请求调度到客户端驱动程序的方式。 类定义创建和初始化框架队列对象的两种方法。 静态方法 CreateInstanceAndInitialize 实例化队列回调类,然后调用用于创建和初始化框架队列对象的 Initialize 方法。 它还指定队列对象的调度选项。

HRESULT 
CMyIoQueue::CreateInstanceAndInitialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice,
    __out CMyIoQueue** Queue
    )
{

    CComObject<CMyIoQueue> *pMyQueue = NULL;
    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    hr = CComObject<CMyIoQueue>::CreateInstance( &pMyQueue );
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to create instance %!hresult!",
                    hr);
        goto Exit;
    }

    hr = pMyQueue->Initialize(FxDevice, MyDevice);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to initialize %!hresult!",
                    hr);
        goto Exit;
    }

    *Queue = pMyQueue;

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

下面的代码示例演示 Initialize 方法的实现。

HRESULT
CMyIoQueue::Initialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice
    )
{
    IWDFIoQueue *fxQueue = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    assert(FxDevice != NULL);
    assert(MyDevice != NULL);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to query IUnknown interface %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDevice->CreateIoQueue(unknown,
                                 FALSE,     // Default Queue?
                                 WdfIoQueueDispatchParallel,  // Dispatch type
                                 TRUE,     // Power managed?
                                 FALSE,     // Allow zero-length requests?
                                 &fxQueue); // I/O queue
    DriverSafeRelease(unknown);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to create framework queue.");
        goto Exit;
    }

    hr = FxDevice->ConfigureRequestDispatching(fxQueue,
                                               WdfRequestDeviceIoControl,
                                               TRUE);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to configure request dispatching %!hresult!.",
                   hr);
        goto Exit;
    }

    m_FxQueue = fxQueue;
    m_Device= MyDevice;

Exit:

    DriverSafeRelease(fxQueue);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

在前面的代码示例中,客户端驱动程序创建框架队列对象。 框架提供队列对象来处理到客户端驱动程序的请求流。

若要创建 对象,客户端驱动程序在上一次调用 IWDFDriver::CreateDevice 中获取的 IWDFDevice 引用 上调用 IWDFDevice::CreateIoQueue

IWDFDevice::CreateIoQueue 调用中,客户端驱动程序在框架创建队列之前指定某些配置选项。 这些选项确定队列是否为电源管理、允许零长度请求,并充当驱动程序的默认队列。 客户端驱动程序提供这组信息:

  • 对其队列回调类的引用

    指定指向其队列回调类 的 IUnknown 指针。 这会在框架队列对象和客户端驱动程序的队列回调对象之间创建合作关系。 当 I/O 管理器收到来自应用程序的新请求时,它会通知框架。 然后,框架使用 IUnknown 指针调用队列回调对象公开的公共方法。

  • 默认或辅助队列

    队列必须是默认队列或辅助队列。 如果框架队列对象充当默认队列,则所有请求都会添加到队列。 辅助队列专用于特定类型的请求。 如果客户端驱动程序请求辅助队列,则驱动程序还必须调用 IWDFDevice::ConfigureRequestDispatching 方法,以指示框架必须放入指定队列的请求类型。 在模板代码中,客户端驱动程序在 bDefaultQueue 参数中传递 FALSE。 这指示 方法创建辅助队列,而不是默认队列。 它稍后调用 IWDFDevice::ConfigureRequestDispatching 以指示队列必须只有设备 I/O 控制请求 (请参阅本节中的示例代码) 。

  • Dispatch 类型

    队列对象的调度类型确定框架如何将请求传递到客户端驱动程序。 传递机制可以是顺序的、并行的,也可以由客户端驱动程序定义的自定义机制。 对于顺序队列,在客户端驱动程序完成上一个请求之前不会传递请求。 在并行调度模式下,框架在请求从 I/O 管理器到达后立即转发请求。 这意味着客户端驱动程序可以在处理另一个请求时接收请求。 在自定义机制中,当驱动程序准备好处理该请求时,客户端会手动从框架队列对象中拉取下一个请求。 在模板代码中,客户端驱动程序请求并行调度模式。

  • 电源管理的队列

    框架队列对象必须与设备的 PnP 和电源状态同步。 如果设备不处于“工作”状态,框架队列对象将停止调度所有请求。 当设备处于“工作”状态时,队列对象将恢复调度。 在电源管理的队列中,同步由框架执行;否则,客户端驱动器必须处理该任务。 在模板代码中,客户端请求电源管理的队列。

  • 允许零长度请求

    客户端驱动程序可以指示框架使用零长度缓冲区完成 I/O 请求,而不是将它们放入队列中。 在模板代码中,客户端请求框架来完成此类请求。

单个框架队列对象可以处理多种类型的请求,例如读取、写入和设备 I/O 控制等。 基于模板代码的客户端驱动程序只能处理设备 I/O 控制请求。 为此,客户端驱动程序的队列回调类实现 IQueueCallbackDeviceIoControl 接口及其 IQueueCallbackDeviceIoControl::OnDeviceIoControl 方法。 这样框架就可以在框架处理设备 I/O 控制请求时调用客户端驱动程序实现 的 IQueueCallbackDeviceIoControl::OnDeviceIoControl

对于其他类型的请求,客户端驱动程序必须实现相应的 IQueueCallbackXxx 接口。 例如,如果客户端驱动程序想要处理读取请求,队列回调类必须实现 IQueueCallbackRead 接口及其 IQueueCallbackRead::OnRead 方法。 有关请求和回调接口的类型的信息,请参阅 I/O 队列事件回调函数

以下代码示例演示 IQueueCallbackDeviceIoControl::OnDeviceIoControl 实现。

VOID
STDMETHODCALLTYPE
CMyIoQueue::OnDeviceIoControl(
    __in IWDFIoQueue *FxQueue,
    __in IWDFIoRequest *FxRequest,
    __in ULONG ControlCode,
    __in SIZE_T InputBufferSizeInBytes,
    __in SIZE_T OutputBufferSizeInBytes
    )
{
    UNREFERENCED_PARAMETER(FxQueue);
    UNREFERENCED_PARAMETER(ControlCode);
    UNREFERENCED_PARAMETER(InputBufferSizeInBytes);
    UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    if (m_Device == NULL) {
        // We don't have pointer to device object
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC!NULL pointer to device object.");
        hr = E_POINTER;
        goto Exit;
    }

    //
    // Process the IOCTLs
    //

Exit:

    FxRequest->Complete(hr);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return;

}

让我们看看队列机制的工作原理。 为了与 USB 设备通信,应用程序首先打开设备的句柄,并通过使用特定控制代码调用 DeviceIoControl 函数发送设备 I/O 控制请求。 根据控制代码的类型,应用程序可以在该调用中指定输入和输出缓冲区。 此调用最终由 I/O 管理器接收,后者会通知框架。 框架创建一个框架请求对象,并将其添加到框架队列对象。 在模板代码中,由于队列对象是使用 WdfIoQueueDispatchParallel 标志创建的,因此只要请求添加到队列,就会立即调用回调。

当框架调用客户端驱动程序的事件回调时,它会将句柄传递给框架请求对象,该对象保存 (请求及其输入和输出缓冲区,) 应用程序发送。 此外,它还将句柄发送到包含该请求的框架队列对象。 在事件回调中,客户端驱动程序会根据需要处理请求。 模板代码只是完成请求。 客户端驱动程序可以执行更多涉及的任务。 例如,如果应用程序请求某些设备信息,在事件回调中,客户端驱动程序可以创建 USB 控制请求并将其发送到 USB 驱动程序堆栈以检索请求的设备信息。 USB 控制传输中讨论了 USB 控制请求。

驱动程序入口源代码

在模板代码中,驱动程序条目在 Dllsup.cpp 中实现。

Dllsup.cpp

在 include 节之后,将声明客户端驱动程序的 GUID 常量。 该 GUID 必须与驱动程序安装文件中的 GUID 匹配, (INF) 。

const CLSID CLSID_Driver =
{0x079e211c,0x8a82,0x4c16,{0x96,0xe2,0x2d,0x28,0xcf,0x23,0xb7,0xff}};

下一个代码块声明客户端驱动程序的类工厂。

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

模板代码使用 ATL 支持来封装复杂的 COM 代码。 类工厂继承包含创建客户端驱动程序所需的所有代码的模板类 CAtlDllModuleT。

以下代码片段演示 DllMain 的实现

extern "C"
BOOL
WINAPI
DllMain(
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    )
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        WPP_INIT_TRACING(MYDRIVER_TRACING_ID);

        g_hInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);

    } else if (dwReason == DLL_PROCESS_DETACH) {
        WPP_CLEANUP();
    }

    return _AtlModule.DllMain(dwReason, lpReserved);
}

如果客户端驱动程序实现 DllMain 函数,Windows 会将 DllMain 视为客户端驱动程序模块的入口点。 Windows 在 WUDFHost.exe 中加载客户端驱动程序模块后调用 DllMain 。 在 Windows 卸载内存中的客户端驱动程序之前,Windows 再次调用 DllMainDllMain 可以在驱动程序级别分配和释放全局变量。 在模板代码中,客户端驱动程序初始化并释放 WPP 跟踪所需的资源,并调用 ATL 类的 DllMain 实现。

以下代码片段演示 DllGetClassObject 的实现。

STDAPI
DllGetClassObject(
    __in REFCLSID rclsid,
    __in REFIID riid,
    __deref_out LPVOID FAR* ppv
    )
{
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}

在模板代码中,类工厂和 DllGetClassObject 在 ATL 中实现。 前面的代码片段只是调用 ATL DllGetClassObject 实现。 通常, DllGetClassObject 必须执行以下任务:

  1. 确保框架传递的 CLSID 是客户端驱动程序的 GUID。 框架从驱动程序的 INF 文件检索客户端驱动程序的 CLSID。 验证时,请确保指定的 GUID 与 INF 中提供的 GUID 匹配。
  2. 实例化客户端驱动程序实现的类工厂。 在模板代码中,它由 ATL 类封装。
  3. 获取指向类工厂的 IClassFactory 接口的指针,并将检索到的指向框架的指针返回。

在内存中加载客户端驱动程序模块后,框架将调用驱动程序提供的 DllGetClassObject 函数。 在框架对 DllGetClassObject 的调用中,框架传递用于标识客户端驱动程序的 CLSID,并请求指向类工厂的 IClassFactory 接口的指针。 客户端驱动程序实现类工厂,该工厂有助于创建驱动程序回调。 因此,客户端驱动程序必须至少包含一个类工厂。 然后,框架调用 IClassFactory::CreateInstance 并请求指向驱动程序回调类的 IDriverEntry 指针。

Exports.def

为了使框架能够调用 DllGetClassObject,客户端驱动程序必须从 .def 文件导出函数。 该文件已包含在 Visual Studio 项目中。

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

在驱动程序项目附带的 Export.def 的上述代码片段中,客户端提供驱动程序模块的名称作为 LIBRARY,并在 EXPORT 下提供 DllGetClassObject 。 有关详细信息,请参阅 使用 DEF 文件从 DLL 导出