技术的乐趣在于分享,欢迎多多交流,多多沟通。
全部博文(877)
分类: Windows平台
2015-05-14 14:11:30
在本主题中,你将了解基于 UMDF 的 USB 客户端驱动程序的源代码。 代码示例是由 Microsoft Visual Studio 2012 附带的 USB 用户模式驱动程序模板生成的。模板代码使用活动模板库 (ATL) 来生成 COM 基础结构。 这里我们不讨论 ATL 以及有关客户端驱动程序中的 COM 实现的详细信息。
有关生成 UMDF 模板代码的说明,请参阅 如何编写第一个 USB 客户端驱动程序 (UMDF)。将在以下部分中讨论模板代码:
讨论模板代码的详细信息之前,让我们看一看头文件 (Internal.h) 中与 UMDF 驱动程序开发有关的一些声明。
Internal.h 包含 Windows 驱动程序工具包 (WDK) 中附带的下列文件:
#include "atlbase.h" #include "atlcom.h" #include "wudfddi.h" #include "wudfusb.h"
Atlbase.h 和 atlcom.h 包含有关 ATL 支持的声明。客户端驱动程序实现的每个类都实现 ATL 类公共 CComObjectRootEx。
UMDF 驱动程序开发总是包含 Wudfddi.h。该头文件包含编译 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 definition of queue. typedef class CMyIoQueue *PCMyIoQueue; // Include the type specific headers. #include "Driver.h" #include "Device.h" #include "IoQueue.h"
安装客户端驱动程序后,Windows 将客户端驱动程序和框架加载到主进程的一个实例中。从这里,框架加载并初始化客户端驱动程序。框架执行以下任务:
当加载和初始化驱动程序时,会发生多个事件并且框架将让客户端驱动程序参与处理这些事件。在客户端驱动程序一方,驱动程序执行以下任务:
框架创建“驱动程序对象”,该对象表示 Windows 加载的客户端驱动程序的实例。客户端驱动程序至少提供一个向框架注册驱动程序的驱动程序回调。
驱动程序回调的完整源代码位于 Driver.h 和 Driver.c 中。
客户端驱动程序必须定义一个实现 IUnknown 和 IDriverEntry 接口的驱动程序回调类。头文件 Driver.h 声明一个名为 CMyDriver 的类,该类定义驱动程序回调。
EXTERN_C const CLSID CLSID_Driver; class CMyDriver : public CComObjectRootEx, public CComCoClass , 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 中释放这些资源。模板代码为OnInitialize 和 OnDeinitialize 方法提供最小实现。
IDriverEntry 的最重要的方法是 IDriverEntry::OnDeviceAdd。框架在创建框架设备对象(将在下一部分中进行讨论)之前,会调用驱动程序的IDriverEntry::OnDeviceAdd 实现。当调用该方法时,框架会传递一个指向驱动程序对象的 IWDFDriver 指针和一个 IWDFDeviceInitialize 指针。客户端驱动程序可以调用 IWDFDeviceInitialize 方法来指定某些配置选项。
通常,客户端驱动程序在其 IDriverEntry::OnDeviceAdd 实现中执行以下任务:
以下代码示例显示模板代码中的 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, 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 implementation
在上一部分中,你简要地了解了客户端驱动程序在 IDriverEntry::OnDeviceAdd 中执行的任务。 下面是有关这些任务的详细信息。客户端驱动程序:
在框架调用客户端驱动程序的 IDriverEntry::OnDeviceAdd 方法的实现中,框架传递一个 IWDFDeviceInitialize 指针。客户端驱动程序使用该指针来为要创建的设备对象指定配置信息。例如,客户端驱动程序指定客户端驱动程序是筛选器驱动程序还是函数驱动程序。要将客户端驱动程序标识为筛选器驱动程序,它会调用IWDFDeviceInitialize::SetFilter。在这种情况下,框架创建一个筛选器设备对象 (FiDO);对于其他情况,则创建一个函数设备对象 (FDO)。你可以通过调用IWDFDeviceInitialize::SetLockingConstraint 设置的另一个选项是同步模式。
如果 IWDFDriver::CreateDevice 调用成功:
设备回调与框架设备对象配对之后,框架和客户端驱动程序处理某些事件,如 PnP 状态和电源状态更改。 例如,当 PnP 管理器启动设备时,会通知框架。然后框架会调用设备回调的 IPnpCallbackHardware::OnPrepareHardware 实现。每个客户端驱动程序必须至少注册一个设备回调对象。
模板代码定义帮助程序方法 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 中的电源策略所有权。
另一个配置选项是指定客户端驱动程序是设备的筛选器驱动程序还是函数驱动程序。请注意,在代码示例中,客户端驱动程序并没有明确指定其首选项。这意味着客户端驱动程序是函数驱动程序并且框架应该在设备堆栈中创建一个 FDO。如果客户端驱动程序想成为筛选器驱动程序,那么驱动程序必须调用 IWDFDeviceInitialize::SetFilter方法。在这种情况下,框架在设备堆栈中创建一个 FiDO。
客户端驱动程序还指定不同步框架对客户端驱动程序回调的任何调用。 客户端驱动程序处理所有同步任务。要指定该首选项,客户端驱动程序会调用IWDFDeviceInitialize::SetLockingConstraint 方法。
接下来,客户端驱动程序通过调用 IUnknown::QueryInterface 来获取指向其设备回调类的 IUnknown 指针。随后,客户端驱动程序调用IWDFDriver::CreateDevice,这会创建框架设备对象并通过使用 IUnknown 指针注册客户端驱动程序的设备回调。
请注意,客户端驱动程序将设备对象的地址(通过 IWDFDriver::CreateDevice 调用接收)存储在设备回调类的一个专用数据成员中,然后通过调用 DriverSafeRelease(在 Internal.h 中定义的内联函数)释放该引用。这是因为框架跟踪设备对象的生存时间。因此,客户端驱动程序不需要保留设备对象的其他引用计数。
模板代码定义公共方法 Configure,该方法注册设备接口 GUID 并设置队列。以下代码示例显示设备回调类 CMyDevice 中的 Configure 方法的定义。创建框架设备对象后, IDriverEntry::OnDeviceAdd 调用 Configure。
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; }
在前面的代码示例中,客户端驱动程序执行两个主要任务:为 I/O 流初始化队列以及注册设备接口 GUID。
会在 CMyIoQueue 类中创建队列并进行配置。第一个任务是通过调用名为 CreateInstanceAndInitialize 的静态方法来实例化该类。客户端驱动程序调用 Configure 来初始化队列。CreateInstanceAndInitialize 和 Configure 是在 CMyIoQueue 中声明的,在本主题的后面部分将对此进行讨论。
客户端驱动程序还调用 IWDFDevice::CreateDeviceInterface 来注册客户端驱动程序的设备接口 GUID。应用程序可以使用该 GUID 向客户端驱动程序发送请求。在 Internal.h 中声明 GUID 常量。
IPnpCallbackHardware implementation and USB-specific tasks
接下来,让我们看一看 Device.cpp 中 IPnpCallbackHardware 接口的实现。
每个设备回调类都必须实现 IPnpCallbackHardware 接口。该接口有两种方法: IPnpCallbackHardware::OnPrepareHardware 和IPnpCallbackHardware::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 方法中创建这些对象。
通常,客户端会执行这些配置任务中的一个或多个任务(具体情况取决于设备的设计):
要执行这些任务,客户端驱动程序可以使用 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。
模板代码将 USB 目标设备对象的地址(通过 IWDFDriver::CreateDevice 调用接收)存储在设备回调类的专用数据成员中,然后通过调用 DriverSafeRelease 释放该引用。USB 目标设备对象的引用计数由框架维护。只要设备对象处于活动状态,该对象就处于活动状态。客户端驱动程序必须在IPnpCallbackHardware::OnReleaseHardware 中释放引用。
客户端驱动程序创建 USB 目标设备对象之后,驱动程序调用 IWDFUsbTargetDevice 方法来执行以下任务:
以下代码示例显示 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, 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*pMyQueue = NULL; HRESULT hr = S_OK; TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry"); hr = CComObject ::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 控制请求(参见本部分中的示例代码)。
分派类型
队列对象的调度类型确定框架向客户端驱动程序传递请求的方式。传递机制可以是顺序机制、并行机制或自定义机制(由客户端驱动程序定义)。对于顺序队列,只有在客户端驱动程序完成上一个请求之后,才会传递下一个请求。在并行调度模式下,当来自 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 必须与驱动程序的安装文件 (INF) 中的 GUID 匹配。
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 视为客户端驱动程序模块的入口点。在 WUDFHost.exe 中加载客户端驱动程序模块之后,Windows 调用 DllMain。Windows 在内存中卸载客户端驱动程序之前会再次调用 DllMain。DllMain 可以在驱动程序级别分配并释放全局变量。在模板代码中,客户端驱动程序初始化并释放 WPP 跟踪所需的资源并调用 ATL 类的 DllMain 实现。
有关如何编写你的 DllMain 的信息,请参阅 实现 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 必须执行以下任务:
将客户端驱动程序模块加载到内存中后,框架调用驱动程序提供的 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 以及 EXPORTS 下的 DllGetClassObject 形式。有关详细信息,请参阅 使用 DEF 文件从 DLL 导出。