如何使用连续读取器从 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 客户端驱动程序中配置连续读取器

  1. 通过调用 WDF_USB_CONTINUOUS_READER_CONFIG_INIT 宏初始化 WDF_USB_CONTINUOUS_READER_CONFIG 结构。

  2. WDF_USB_CONTINUOUS_READER_CONFIG 结构中指定其配置选项。

  3. 调用 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 中讨论了完成例程的实现。
  • 指向驱动程序定义的上下文的指针。
  • 可在单个传输中从设备读取的字节数。 客户端驱动程序可以通过调用 WdfUsbInterfaceGetConfiguredPipeWdfUsbTargetPipeGetInformation 方法,在 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 时指定数据包的大小。 然后,驱动程序必须通过设置 headerLengthTrailerLength 成员 来指定页眉和页脚的长度WDF_USB_CONTINUOUS_READER_CONFIG。 框架使用这些值来计算有效负载两侧的字节偏移量。 从终结点读取有效负载数据时,框架会将该数据存储在偏移之间的缓冲区部分。

实现完成例程

每次完成请求时,框架都会调用客户端驱动程序实现的完成例程。 框架传递读取的字节数和 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。 此值向框架指示它必须重置管道,然后重新启动连续读取器。

或者,如果管道上出现停止情况,客户端驱动程序可以返回 FALSE 并提供错误恢复机制。 例如,驱动程序可以检查 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));
}

前面的示例演示 了 EvtDeviceD0EntryEvtDeviceD0Exit 回调例程的实现。 WdfIoTargetStop 的 Action 参数允许客户端驱动程序在设备离开工作状态时决定队列中挂起请求的操作。 在示例中,驱动程序指定 WdfIoTargetCancelSentIo。 该选项指示框架取消队列中的所有挂起请求。 或者,驱动程序可以指示框架在停止 I/O 目标之前等待挂起的请求完成,或者保留挂起的请求并在 I/O 目标重启时恢复。

在 UMDF 客户端驱动程序中使用连续读取器

在开始使用连续读取器之前,必须在 IPnpCallbackHardware::OnPrepareHardware 方法的实现中配置读取器。 获取指向与 IN 终结点关联的目标管道对象的 IWDFUsbTargetPipe 接口的指针后,请执行以下步骤:

在 UMDF 客户端驱动程序中配置连续读取器

  1. 在目标管道对象上调用 QueryInterface (IWDFUsbTargetPipe) 并查询 IWDFUsbTargetPipe2 接口。

  2. 在设备回调对象上调用 QueryInterface 并查询 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口。 若要使用连续读取器,必须实现 IUsbTargetPipeContinuousReaderCallbackReadComplete。 本主题稍后将介绍实现。

  3. 如果已实现失败回调,请在设备回调对象上调用 QueryInterface 并查询 IUsbTargetPipeContinuousReaderCallbackReadersFailed 接口。 本主题稍后将介绍实现。

  4. 调用 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 接口的指针后,请执行以下步骤:

实现状态管理

  1. IPnpCallbackHardware::OnPrepareHardware 的实现中,调用目标管道对象上的 QueryInterface (IWDFUsbTargetPipe) 并查询 IWDFIoTargetStateManagement 接口。 将引用存储在设备回调类的成员变量中。

  2. 在设备回调对象上实现 IPnpCallback 接口。

  3. IPnpCallback::OnD0Entry 方法的实现中,调用 IWDFIoTargetStateManagement::Start 以启动连续读取器。

  4. IPnpCallback::OnD0Exit 方法的实现中,调用 IWDFIoTargetStateManagement::Stop 以停止连续读取器。

设备 (D0) 进入工作状态后,框架会调用客户端驱动程序提供的 D0-entry 回调方法来启动目标管道对象。 当设备离开 D0 状态时,框架将调用 D0-exit 回调方法。 目标管道对象完成客户端驱动程序配置的挂起读取请求数,并停止接受新请求。 以下示例代码在设备回调对象上实现 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 提供完成回调

  1. 在设备回调对象上实现 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口。

  2. 确保设备回调对象的 QueryInterface 实现递增回调对象的引用计数,然后返回 IUsbTargetPipeContinuousReaderCallbackReadComplete 接口指针。

  3. IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion 方法的实现中,访问从管道读取的数据。 pMemory 参数指向由包含数据的框架分配的内存。 可以调用 IWDFMemory::GetDataBuffer 来获取包含数据的缓冲区。 缓冲区包含标头,但 OnReaderCompletionNumBytesTransferred 参数指示的数据长度不包括标头长度。 标头长度由客户端驱动程序指定,同时在驱动程序调用 IWDFUsbTargetPipe2::ConfigureContinuousReader 时配置连续读取器。

  4. 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::StartIWDFIoTargetStateManagement::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 管道并重启连续读取器。