共用方式為


如何使用連續讀取器從USB管道讀取數據

本主題描述 WDF 提供的連續讀取器物件。 本主題中的程式提供如何設定物件,並使用它從USB管道讀取數據的逐步指示。

Windows Driver Framework (WDF) 提供稱為 連續讀取器的特殊物件。 只要有可用的數據,此物件可讓USB用戶端驅動程式從大量和中斷端點持續讀取數據。 若要使用讀取器,用戶端驅動程序必須具有與驅動程式讀取數據端點相關聯的USB目標管道物件的句柄。 端點必須位於使用中組態中。 您可以透過下列兩種方式的其中一種方式來啟用組態:選取USB組態,或變更目前組態中的替代設定。 如需這些作業的詳細資訊,請參閱 如何選取USB裝置 的組態和 如何在USB介面中選取替代設定。

建立連續讀取器之後,用戶端驅動程式可以視需要啟動和停止讀取器。 持續讀取器,可確保目標管道物件上一律提供讀取要求,而且客戶端驅動程式一律準備好從端點接收數據。

連續讀取器不會自動由架構管理。 這表示當裝置進入較低電源狀態時,用戶端驅動程式必須停止讀取器,並在裝置進入工作狀態時重新啟動讀取器。

本文會利用:

在您開始使用 Intune 之前

在用戶端驅動程式可以使用連續讀取器之前,請確定符合下列需求:

  • 您的 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 然後,驅動程式必須藉由設定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。 這個值會向架構指出它必須重設管道,然後重新啟動連續讀取器。

或者,如果管道發生停滯狀況,客戶端驅動程式可以傳回 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));
}

上述範例顯示 EvtDeviceD0Entry EvtDeviceD0Exit 回呼例程的實作。 WdfIoTargetStopAction 參數可讓用戶端驅動程式在裝置離開工作狀態時,決定佇列中擱置要求的動作。 在此範例中,驅動程式會 指定 WdfIoTargetCancelSentIo。 此選項會指示架構取消佇列中的所有擱置要求。 或者,驅動程式可以指示架構等待擱置的要求完成,再停止 I/O 目標,或保留擱置的要求,並在 I/O 目標重新啟動時繼續。

在 UMDF 用戶端驅動程式中使用連續讀取器

開始使用連續讀取器之前,您必須在 IPnpCallbackHardware::OnPrepareHardware 方法的實作中設定讀取器。 取得與 IN 端點相關聯之目標管道物件的 IWDFUsbTargetPipe 介面指標之後,請執行下列步驟:

在 UMDF 用戶端驅動程式中設定連續讀取器

  1. 在目標管道物件上呼叫 QueryInterfaceIWDFUsbTargetPipe),並查詢 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 實作中,呼叫目標管道物件 (IWDFUsbTargetPipe) 上的 QueryInterface,並查詢 IWDFIoTargetStateManagement 介面。 將參考儲存在裝置回呼類別的成員變數中。

  2. 裝置回呼對象上實作 IPnpCallback 介面。

  3. 在 IPnpCallback::OnD0Entry 方法的實作中,呼叫 IWDFIoTargetStateManagement::Start 以啟動連續讀取器。

  4. 在 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 來提供完成回呼

  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::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管道並重新啟動連續讀取器。