如何使用连续读取器从 USB 管道读取数据
本主题介绍 WDF 提供的连续读取器对象。 本主题中的过程提供了有关如何配置对象并使用它从 USB 管道读取数据的分步说明。
Windows 驱动程序框架 (WDF) 提供一 个名为连续读取器的专用对象。 此对象使 USB 客户端驱动程序能够连续读取批量和中断终结点中的数据,只要有数据可用。 若要使用读取器,客户端驱动程序必须具有与驱动程序从中读取数据的终结点关联的 USB 目标管道对象的句柄。 终结点必须位于活动配置中。 可以通过以下两种方式之一激活配置:选择 USB 配置或更改当前配置中的备用设置。 有关这些操作的详细信息,请参阅 如何为 USB 设备 选择配置以及如何 在 USB 接口中选择备用设置。
创建连续读取器后,客户端驱动程序可以根据需要启动和停止读取器。 连续读取器,确保读取请求始终在目标管道对象上可用,并且客户端驱动程序始终准备好从终结点接收数据。
连续读取器不会自动由框架管理。 这意味着当设备进入较低电源状态时,客户端驱动程序必须停止读取器,并在设备进入工作状态时重启读取器。
本文利用:
准备工作
在客户端驱动程序可以使用连续读取器之前,请确保满足以下要求:
USB 设备必须具有 IN 终结点。 检查 USBView 中的设备配置。 Usbview.exe是一个应用程序,可用于浏览所有 USB 控制器和连接到它们的 USB 设备。 通常,USBView 安装在 Windows 驱动程序工具包(WDK)的调试器 文件夹中。
客户端驱动程序必须已创建框架 USB 目标设备对象。
如果使用 Microsoft Visual Studio Professional 2012 随附的 USB 模板,则模板代码会执行这些任务。 模板代码会获取目标设备对象的句柄并将其存储在设备上下文中。
KMDF 客户端驱动程序:
KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceCreateWithParameters 方法来获取 WDFUSBDEVICE 句柄。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (KMDF) 中的“设备源代码”。
UMDF 客户端驱动程序:
UMDF 客户端驱动程序必须通过查询框架目标设备对象获取 IWDFUsbTargetDevice 指针。 有关详细信息,请参阅了解 USB 客户端驱动程序代码结构 (UMDF) 中的“IPnpCallbackHardware 实现和特定于 USB 的任务”。
设备必须具有活动配置。
如果使用 USB 模板,代码将在每个接口中选择第一个配置和默认备用设置。 有关如何更改备用设置的信息,请参阅 如何在 USB 接口中选择备用设置。
KMDF 客户端驱动程序:
KMDF 客户端驱动程序必须调用 WdfUsbTargetDeviceSelectConfig 方法。
UMDF 客户端驱动程序:
对于 UMDF 客户端驱动程序,框架为该配置中的每个接口选择第一个配置和默认备用设置。
客户端驱动程序必须具有 IN 终结点的框架目标管道对象的句柄。 有关详细信息,请参阅 如何枚举 USB 管道。
在 KMDF 客户端驱动程序中使用连续读取器
在开始使用连续读取器之前,必须通过初始化 WDF_USB_CONTINUOUS_READER_CONFIG 结构来配置它。
在 KMDF 客户端驱动程序中配置连续读取器
通过调用WDF_USB_CONTINUOUS_READER_CONFIG_INIT宏初始化WDF_USB_CONTINUOUS_READER_CONFIG结构。
在 WDF_USB_CONTINUOUS_READER_CONFIG 结构中指定其配置选项。
调用 WdfUsbTargetPipeConfigContinuousReader 方法。
以下示例代码为指定的目标管道对象配置连续读取器。
NTSTATUS FX3ConfigureContinuousReader( _In_ WDFDEVICE Device, _In_ WDFUSBPIPE Pipe) { NTSTATUS status; PDEVICE_CONTEXT pDeviceContext; WDF_USB_CONTINUOUS_READER_CONFIG readerConfig; PPIPE_CONTEXT pipeContext; PAGED_CODE(); pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device); pipeContext = GetPipeContext (Pipe); WDF_USB_CONTINUOUS_READER_CONFIG_INIT( &readerConfig, FX3EvtReadComplete, pDeviceContext, pipeContext->MaxPacketSize); readerConfig.EvtUsbTargetPipeReadersFailed=FX3EvtReadFailed; status = WdfUsbTargetPipeConfigContinuousReader( Pipe, &readerConfig); if (!NT_SUCCESS (status)) { TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "%!FUNC! WdfUsbTargetPipeConfigContinuousReader failed 0x%x", status); goto Exit; } Exit: return status; }
客户端驱动程序在枚举活动设置中的目标管道对象后,在 EvtDevicePrepareHardware 回调函数中配置连续读取器。
在前面的示例中,客户端驱动程序以两种方式指定其配置选项。 首先通过调用WDF_USB_CONTINUOUS_READER_CONFIG_INIT,然后通过设置WDF_USB_CONTINUOUS_READER_CONFIG成员。 请注意WDF_USB_CONTINUOUS_READER_CONFIG_INIT的参数。 这些值是必需的。 在此示例中,客户端驱动程序指定:
- 指向驱动程序实现的完成例程的指针。 框架在完成读取请求时调用此例程。 在完成例程中,驱动程序可以访问包含已读取数据的内存位置。 完成例程的实现在步骤 2 中进行了讨论。
- 指向驱动程序定义的上下文的指针。
- 可以在单个传输中从设备读取的字节数。 客户端驱动程序可以通过调用 WdfUsbInterfaceGetConfiguredPipe 或 WdfUsbTargetPipeGetInformation 方法获取WDF_USB_PIPE_INFORMATION结构中的信息。 有关详细信息,请参阅 如何枚举 USB 管道。
WDF_USB_CONTINUOUS_READER_CONFIG_INIT将连续读取器配置为使用 NumPendingReads 的默认值。 该值确定框架添加到挂起队列的读取请求数。 默认值已确定为许多处理器配置上的许多设备提供相当良好的性能。
除了WDF_USB_CONTINUOUS_READER_CONFIG_INIT中指定的配置参数外,该示例还在WDF_USB_CONTINUOUS_READER_CONFIG中设置故障例程。 此失败例程是可选的。
除了故障例程之外,还有WDF_USB_CONTINUOUS_READER_CONFIG客户端驱动程序可用于指定传输缓冲区布局的其他成员。 例如,请考虑使用连续读取器接收网络数据包的网络驱动程序。 每个数据包都包含标头、有效负载和页脚数据。 若要描述数据包,驱动程序必须首先在其调用中指定数据包的大小以 WDF_USB_CONTINUOUS_READER_CONFIG_INIT。 然后,驱动程序必须通过设置WDF_USB_CONTINUOUS_READER_CONFIG的 HeaderLength 和 TrailerLength 成员来指定页眉和页脚的长度。 框架使用这些值计算有效负载两侧的字节偏移量。 从终结点读取有效负载数据时,框架会将该数据存储在偏移量之间的缓冲区部分。
实现完成例程
每次请求完成时,框架都会调用客户端驱动程序实现的完成例程。 框架传递读取的字节数和一个 WDFMEMORY 对象,其缓冲区包含从管道读取的数据。
以下示例代码显示了完成例程实现。
EVT_WDF_USB_READER_COMPLETION_ROUTINE FX3EvtReadComplete;
VOID FX3EvtReadComplete(
__in WDFUSBPIPE Pipe,
__in WDFMEMORY Buffer,
__in size_t NumBytesTransferred,
__in WDFCONTEXT Context
)
{
PDEVICE_CONTEXT pDeviceContext;
PVOID requestBuffer;
pDeviceContext = (PDEVICE_CONTEXT)Context;
if (NumBytesTransferred == 0)
{
return;
}
requestBuffer = WdfMemoryGetBuffer(Buffer, NULL);
if (Pipe == pDeviceContext->InterruptPipe)
{
KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
"Interrupt endpoint: %s.\n",
requestBuffer ));
}
return;
}
每次请求完成时,框架都会调用客户端驱动程序实现的完成例程。 框架为每个读取操作分配一个内存对象。 在完成例程中,框架将读取的字节数和 WDFMEMORY 句柄传递给内存对象。 内存对象缓冲区包含从管道读取的数据。 客户端驱动程序不得释放内存对象。 框架在每个完成例程返回后释放对象。 如果客户端驱动程序想要存储收到的数据,驱动程序必须在完成例程中复制缓冲区的内容。
实现失败例程
框架调用客户端驱动程序实现的失败例程,以通知驱动程序连续读取器在处理读取请求时报告了错误。 框架将指针传递给请求失败的目标管道对象和错误代码值。 根据这些错误代码值,驱动程序可以实现其错误恢复机制。 驱动程序还必须返回一个适当的值,该值指示框架是否应重启连续读取器。
以下示例代码演示失败例程实现。
EVT_WDF_USB_READERS_FAILED FX3EvtReadFailed;
BOOLEAN
FX3EvtReadFailed(
WDFUSBPIPE Pipe,
NTSTATUS Status,
USBD_STATUS UsbdStatus
)
{
UNREFERENCED_PARAMETER(Status);
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! ReadersFailedCallback failed NTSTATUS 0x%x, UsbdStatus 0x%x\n",
status,
UsbdStatus);
return TRUE;
}
在前面的示例中,驱动程序返回 TRUE。 此值向框架指示它必须重置管道,然后重启连续读取器。
或者,客户端驱动程序可以返回 FAL标准版并在管道上出现停止条件时提供错误恢复机制。 例如,驱动程序可以检查 USBD 状态,并发出重置管道请求以清除停止条件。
有关管道中的错误恢复的信息,请参阅 如何从 USB 管道错误中恢复。
启动和停止连续读取器
指示框架在设备进入工作状态时启动连续读取器;设备离开工作状态时停止读取器。 调用这些方法,并将目标管道对象指定为 I/O 目标对象。
连续读取器不会自动由框架管理。 因此,当设备电源状态发生更改时,客户端驱动程序必须显式启动或停止目标管道对象。 驱动程序在驱动程序的 EvtDeviceD0Entry 实现中调用 WdfIoTargetStart。 此调用可确保队列仅在设备处于工作状态时传递请求。 相反,驱动程序在驱动程序 EvtDeviceD0Exit 实现中调用 WdfIoTargetStop,以便在设备进入较低电源状态时队列停止传送请求。
以下示例代码为指定的目标管道对象配置连续读取器。
EVT_WDF_DEVICE_D0_ENTRY FX3EvtDeviceD0Entry;
NTSTATUS FX3EvtDeviceD0Entry(
__in WDFDEVICE Device,
__in WDF_POWER_DEVICE_STATE PreviousState
)
{
PDEVICE_CONTEXT pDeviceContext;
NTSTATUS status;
PAGED_CODE();
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
status = WdfIoTargetStart (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe));
if (!NT_SUCCESS (status))
{
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"%!FUNC! Could not start interrupt pipe failed 0x%x", status);
}
}
EVT_WDF_DEVICE_D0_EXIT FX3EvtDeviceD0Exit;
NTSTATUS FX3EvtDeviceD0Exit(
__in WDFDEVICE Device,
__in WDF_POWER_DEVICE_STATE TargetState
)
{
PDEVICE_CONTEXT pDeviceContext;
NTSTATUS status;
PAGED_CODE();
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
WdfIoTargetStop (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe), WdfIoTargetCancelSentIo));
}
前面的示例显示了 EvtDeviceD0Entry 和 EvtDeviceD0Exit 回调例程的实现。 WdfIoTargetStop 的 Action 参数允许客户端驱动程序在设备离开工作状态时决定队列中挂起请求的操作。 在此示例中,驱动程序指定 WdfIoTargetCancelSentIo。 此选项指示框架取消队列中的所有挂起请求。 或者,驱动程序可以指示框架等待挂起的请求在停止 I/O 目标之前完成,或保留挂起的请求并在 I/O 目标重启时恢复。
在 UMDF 客户端驱动程序中使用连续读取器
开始使用连续读取器之前,必须在 IPnpCallbackHardware::OnPrepareHardware 方法的 实现中配置读取器 。 获取指向 与 IN 终结点关联的目标管道对象的 IWDFUsbTargetPipe 接口的指针后,请执行以下步骤:
在 UMDF 客户端驱动程序中配置连续读取器
在目标管道对象(IWDFUsbTargetPipe)上调用 QueryInterface,并查询 IWDFUsbTargetPipe2 接口。
在设备回调对象上调用 QueryInterface,并查询 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口。 若要使用连续读取器,必须实现 IUsbTargetPipeContinuousReaderCallbackReadComplete。 本主题稍后将介绍实现。
在设备回调对象上调用 QueryInterface,并在实现失败回调后查询 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口。 本主题稍后将介绍实现。
调用 IWDFUsbTargetPipe2::ConfigureContinuousReader 方法并指定配置参数,例如标头、预告片、挂起请求数以及对完成和失败回调方法的引用。
该方法为目标管道对象配置连续读取器。 连续读取器创建队列,以便在从目标管道对象发送和接收读取请求时管理一组读取请求。
以下示例代码为指定的目标管道对象配置连续读取器。 该示例假定调用方指定的目标管道对象与 IN 终结点相关联。 连续读取器配置为读取USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE字节;使用框架使用的默认挂起请求数;调用客户端驱动程序提供的完成和失败回调方法。 接收的缓冲区不包含任何标头或预告片数据。
HRESULT CDeviceCallback::ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe)
{
if (!pFxPipe)
{
return E_INVALIDARG;
}
IUsbTargetPipeContinuousReaderCallbackReadComplete *pOnCompletionCallback = NULL;
IUsbTargetPipeContinuousReaderCallbackReadersFailed *pOnFailureCallback = NULL;
IWDFUsbTargetPipe2* pFxUsbPipe2 = NULL;
HRESULT hr = S_OK;
// Set up the continuous reader to read from the target pipe object.
//Get a pointer to the target pipe2 object.
hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&pFxUsbPipe2));
if (FAILED(hr))
{
goto ConfigureContinuousReaderExit;
}
//Get a pointer to the completion callback.
hr = QueryInterface(IID_PPV_ARGS(&pOnCompletionCallback));
if (FAILED(hr))
{
goto ConfigureContinuousReaderExit;
}
//Get a pointer to the failure callback.
hr = QueryInterface(IID_PPV_ARGS(&pOnFailureCallback));
if (FAILED(hr))
{
goto ConfigureContinuousReaderExit;
}
//Get a pointer to the target pipe2 object.
hr = pFxUsbPipe2->ConfigureContinuousReader (
USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE, //size of data to be read
0, //Header
0, //Trailer
0, // Number of pending requests queued by WDF
NULL, // Cleanup callback. Not provided.
pOnCompletionCallback, //Completion routine.
NULL, //Completion routine context. Not provided.
pOnFailureCallback); //Failure routine. Not provided
if (FAILED(hr))
{
goto ConfigureContinuousReaderExit;
}
ConfigureContinuousReaderExit:
if (pOnFailureCallback)
{
pOnFailureCallback->Release();
pOnFailureCallback = NULL;
}
if (pOnCompletionCallback)
{
pOnCompletionCallback->Release();
pOnCompletionCallback = NULL;
}
if (pFxUsbPipe2)
{
pFxUsbPipe2->Release();
pFxUsbPipe2 = NULL;
}
return hr;
}
接下来,指定目标管道对象的状态,当设备进入并退出工作状态时(D0)。
如果客户端驱动程序使用电源管理的队列将请求发送到管道,则队列仅在设备处于 D0 状态时才传递请求。 如果设备的电源状态从 D0 更改为较低电源状态(在 D0 退出时),目标管道对象将完成挂起的请求,队列停止向目标管道对象提交请求。 因此,不需要客户端驱动程序来启动和停止目标管道对象。
连续读取器不使用电源管理的队列来提交请求。 因此,当设备电源状态发生更改时,必须显式启动或停止目标管道对象。 若要更改目标管道对象的状态,可以使用 框架实现的 IWDFIoTargetStateManagement 接口。 获取指向 与 IN 终结点关联的目标管道对象的 IWDFUsbTargetPipe 接口的指针后,请执行以下步骤:
实现状态管理
在 IPnpCallbackHardware::OnPrepareHardware 的实现中,在目标管道对象(IWDFUsbTargetPipe)上调用 QueryInterface,并查询 IWDFIoTargetStateManagement 接口。 将引用存储在设备回调类的成员变量中。
在 设备回调对象上实现 IPnpCallback 接口。
在 IPnpCallback::OnD0Entry 方法的实现中,调用 IWDFIoTargetStateManagement::Start 以启动连续读取器。
在 IPnpCallback::OnD0Exit 方法的实现中,调用 IWDFIoTargetStateManagement::Stop 以停止连续读取器。
设备进入工作状态(D0)后,框架将调用客户端驱动程序提供的 D0 条目回调方法来启动目标管道对象。 当设备离开 D0 状态时,框架将调用 D0 退出回调方法。 目标管道对象完成由客户端驱动程序配置的挂起读取请求数,并停止接受新请求。 以下示例代码在设备回调对象上实现 IPnpCallback 接口。
class CDeviceCallback :
public IPnpCallbackHardware,
public IPnpCallback,
{
public:
CDeviceCallback();
~CDeviceCallback();
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice* pWdfDevice);
private:
LONG m_cRefs;
IWDFUsbTargetPipe* m_pFxUsbPipe;
IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;
HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};
以下示例代码演示如何获取指向 IPnpCallback::OnPrepareHardware 方法中目标管道对象的 IWDFIoTargetStateManagement 接口的指针
//Enumerate the endpoints and get the interrupt pipe.
for (UCHAR index = 0; index < NumEndpoints; index++)
{
hr = pFxInterface->RetrieveUsbPipeObject(index, &pFxPipe);
if (SUCCEEDED (hr) && pFxPipe)
{
if ((pFxPipe->IsInEndPoint()) && (pFxPipe->GetType()==UsbdPipeTypeInterrupt))
{
//Pipe is for an interrupt IN endpoint.
hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&m_pFxIoTargetInterruptPipeStateMgmt));
if (m_pFxIoTargetInterruptPipeStateMgmt)
{
m_pFxUsbPipe = pFxPipe;
break;
}
}
else
{
//Pipe is NOT for an interrupt IN endpoint.
pFxPipe->Release();
pFxPipe = NULL;
}
}
else
{
//Pipe not found.
}
}
以下示例代码演示如何获取指向 IPnpCallbackHardware::OnPrepareHardware 方法中目标管道对象的 IWDFIoTargetStateManagement 接口的指针。
HRESULT CDeviceCallback::OnD0Entry(
IWDFDevice* pWdfDevice,
WDF_POWER_DEVICE_STATE previousState
)
{
if (!m_pFxIoTargetInterruptPipeStateMgmt)
{
return E_FAIL;
}
HRESULT hr = m_pFxIoTargetInterruptPipeStateMgmt->Start();
if (FAILED (hr))
{
goto OnD0EntryExit;
}
OnD0EntryExit:
return hr;
}
HRESULT CDeviceCallback::OnD0Exit(
IWDFDevice* pWdfDevice,
WDF_POWER_DEVICE_STATE previousState
)
{
if (!m_pFxIoTargetInterruptPipeStateMgmt)
{
return E_FAIL;
}
// Stop the I/O target always succeeds.
(void)m_pFxIoTargetInterruptPipeStateMgmt->Stop(WdfIoTargetCancelSentIo);
return S_OK;
}
连续读取器完成读取请求后,客户端驱动程序必须提供在请求成功完成读取请求时获得通知的方法。 客户端驱动程序必须将此代码添加到设备回调对象。
通过实现 IUsbTargetPipeContinuousReaderCallbackReadComplete 提供完成回调
在 设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口。
确保 设备回调对象的 QueryInterface 实现递增回调对象的引用计数,然后返回 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口指针。
在 IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion 方法的实现中,访问从管道读取的数据。 pMemory 参数指向包含数据的框架分配的内存。 可以调用 IWDFMemory::GetDataBuffer 来获取包含数据的缓冲区。 缓冲区包含标头,但 OnReaderCompletion 的 NumBytesTransferred 参数指示的数据长度不包括标头长度。 标头长度由客户端驱动程序指定,同时在驱动程序调用 IWDFUsbTargetPipe2::ConfigureContinuousReader 时配置连续读取器。
在 IWDFUsbTargetPipe2::ConfigureContinuousReader 方法的 pOnCompletion 参数中提供指向完成回调的指针。
每当设备上的终结点上提供数据时,目标管道对象都会完成读取请求。 如果读取请求成功完成,则框架通过调用 IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion 通知客户端驱动程序。 否则,当目标管道对象报告读取请求上的错误时,框架将调用客户端驱动程序提供的失败回调。
以下示例代码在设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口。
class CDeviceCallback :
public IPnpCallbackHardware,
public IPnpCallback,
public IUsbTargetPipeContinuousReaderCallbackReadComplete
{
public:
CDeviceCallback();
~CDeviceCallback();
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice* pWdfDevice);
virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);
private:
LONG m_cRefs;
IWDFUsbTargetPipe* m_pFxUsbPipe;
IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;
HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};
以下示例代码显示了设备回调对象的 QueryInterface 实现。
HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
if (ppvObject == NULL)
{
return E_INVALIDARG;
}
*ppvObject = NULL;
HRESULT hr = E_NOINTERFACE;
if( IsEqualIID(riid, __uuidof(IPnpCallbackHardware)) || IsEqualIID(riid, __uuidof(IUnknown)) )
{
*ppvObject = static_cast<IPnpCallbackHardware*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
if( IsEqualIID(riid, __uuidof(IPnpCallback)))
{
*ppvObject = static_cast<IPnpCallback*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
if( IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
{
*ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
return hr;
}
以下示例代码演示如何从 IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion 返回的缓冲区获取数据。 每当目标管道对象成功完成读取请求时,框架都会调用 OnReaderCompletion。 该示例获取包含数据的缓冲区,并输出调试器输出中的内容。
VOID CDeviceCallback::OnReaderCompletion(
IWDFUsbTargetPipe* pPipe,
IWDFMemory* pMemory,
SIZE_T NumBytesTransferred,
PVOID Context)
{
if (pPipe != m_pFxUsbInterruptPipe)
{
return;
}
if (NumBytesTransferred == 0)
{
// NumBytesTransferred is zero.
return;
}
PVOID pBuff = NULL;
LONG CurrentData = 0;
char data[20];
pBuff = pMemory->GetDataBuffer(NULL);
if (pBuff)
{
CopyMemory(&CurrentData, pBuff, sizeof(CurrentData));
sprintf_s(data, 20, "%d\n", CurrentData);
OutputDebugString(data);
pBuff = NULL;
}
else
{
OutputDebugString(TEXT("Unable to get data buffer."));
}
}
完成读取请求时,当目标管道对象发生故障时,客户端驱动程序可以从框架获取通知。 若要获取通知,客户端驱动程序必须实现故障回调,并在配置连续读取器时提供指向回调的指针。 以下过程介绍如何实现失败回调。
通过实现 IUsbTargetPipeContinuousReaderCallbackReadersFailed 提供失败回调
在 设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口。
确保 设备回调对象的 QueryInterface 实现递增回调对象的引用计数,然后返回 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口指针。
在 IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 方法的实现中,提供失败读取请求的错误处理。
如果连续读取器无法完成读取请求,并且客户端驱动程序提供失败回调,则框架将 调用 IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 方法。 框架在 hrStatus 参数中提供 HRESULT 值,该值指示目标管道对象中发生的错误代码。 根据该错误代码,可能会提供某些错误处理。 例如,如果希望框架重置管道,然后重启连续读取器,请确保回调返回 TRUE。
注意不要在失败回调中调用 IWDFIoTargetStateManagement::Start 和 IWDFIoTargetStateManagement::Stop。
在 IWDFUsbTargetPipe2::ConfigureContinuousReader 方法的 pOnFailure 参数中提供指向失败回调的指针。
以下示例代码在设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口。
class CDeviceCallback :
public IPnpCallbackHardware,
public IPnpCallback,
public IUsbTargetPipeContinuousReaderCallbackReadComplete,
public IUsbTargetPipeContinuousReaderCallbackReadersFailed
{
public:
CDeviceCallback();
~CDeviceCallback();
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);
virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice* pWdfDevice, WDF_POWER_DEVICE_STATE previousState);
virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice* pWdfDevice);
virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice* pWdfDevice);
virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);
virtual BOOL STDMETHODCALLTYPE OnReaderFailure(IWDFUsbTargetPipe * pPipe, HRESULT hrCompletion);
private:
LONG m_cRefs;
IWDFUsbTargetPipe* m_pFxUsbInterruptPipe;
IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;
HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
HRESULT RetrieveUSBDeviceDescriptor (IWDFUsbTargetDevice* pUSBTargetDevice, PUSB_DEVICE_DESCRIPTOR DescriptorHeader, PULONG cbDescriptor);
HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};
以下示例代码显示了设备回调对象的 QueryInterface 实现。
HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
if (ppvObject == NULL)
{
return E_INVALIDARG;
}
*ppvObject = NULL;
HRESULT hr = E_NOINTERFACE;
if( IsEqualIID(riid, __uuidof(IPnpCallbackHardware)) || IsEqualIID(riid, __uuidof(IUnknown)) )
{
*ppvObject = static_cast<IPnpCallbackHardware*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
if( IsEqualIID(riid, __uuidof(IPnpCallback)))
{
*ppvObject = static_cast<IPnpCallback*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
if( IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
{
*ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
if( IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadersFailed)))
{
*ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadersFailed*>(this);
reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
hr = S_OK;
}
return hr;
}
以下示例代码演示失败回调的实现。 如果读取请求失败,该方法将在调试器中打印框架报告的错误代码,并指示框架重置管道,然后重启连续读取器。
BOOL CDeviceCallback::OnReaderFailure(
IWDFUsbTargetPipe * pPipe,
HRESULT hrCompletion
)
{
UNREFERENCED_PARAMETER(pPipe);
UNREFERENCED_PARAMETER(hrCompletion);
return TRUE;
}
如果客户端驱动程序未提供故障回调并发生错误,框架将重置 USB 管道并重启连续读取器。