在 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 提供失败回调
1.在 设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口。
2.确保 设备回调对象的 QueryInterface 实现递增回调对象的引用计数,然后返回 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口指针。
3.在 IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 方法的实现中,提供失败读取请求的错误处理。
如果连续读取器无法完成读取请求,并且客户端驱动程序提供失败回调,则框架将 调用 IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure 方法。 框架在 hrStatus 参数中提供 HRESULT 值,该值指示目标管道对象中发生的错误代码。 根据该错误代码,可能会提供某些错误处理。 例如,如果希望框架重置管道,然后重启连续读取器,请确保回调返回 TRUE。
注意不要在失败回调中调用 IWDFIoTargetStateManagement::Start 和 IWDFIoTargetStateManagement::Stop。
4.在 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 管道并重启连续读取器。