共用方式為


如何列舉 USB 管道

本文提供 USB 管道的概觀,並說明 USB 用戶端驅動程式從 USB 驅動程式堆疊取得管道句柄所需的步驟。

USB 端點是客戶端驅動程式將數據傳送至或接收資料之裝置中的緩衝區。 若要傳送或接收數據,用戶端驅動程式會將I/O傳輸要求提交至USB驅動程式堆疊,此堆疊會將數據呈現給主機控制器。 然後,主機控制器會遵循特定通訊協定 (,視端點類型而定:大量、中斷或連續) ,以建置傳送數據到裝置或從裝置傳送數據的要求。 數據傳輸的所有詳細數據都會從客戶端驅動程式擷取。 只要客戶端驅動程式提交格式正確的要求,USB 驅動程式堆疊就會處理要求,並將數據傳輸至裝置。

在裝置設定期間,USB 驅動程式堆疊會在主機端建立 USB管道 () ,以針對USB介面中定義的每個裝置端點及其作用中的替代設定。 USB 管道是主機控制器與端點之間的通道。 針對客戶端驅動程式,管道是端點的邏輯抽象概念。 若要傳送數據傳輸,驅動程式必須取得與傳送目標端點相關聯的管道句柄。 當驅動程式想要中止傳輸或重設管道時,如果發生錯誤狀況,也需要管道控點。

管道的所有屬性都是衍生自相關聯的端點描述元。 例如,根據端點的類型,USB 驅動程式堆疊會指派管道的類型。 針對大量端點,USB 驅動程式堆疊會建立大量管道;如果是等時端點,則會建立等時線。 另一個重要屬性是主機控制器可以傳送至要求中端點的數據量。 視該值而定,用戶端驅動程式必須決定傳輸緩衝區的配置。

Windows Driver Foundation (WDF) 在 Kernel-Mode Driver Framework (KMDF) User-Mode Driver Framework (UMDF ) 中提供特製化 I/O 目標物件,以簡化用戶端驅動程式的許多設定工作。 用戶端驅動程式可以使用這些物件來擷取目前組態的相關信息,例如介面數目、每個介面內的替代設定,以及其端點。 其中一個對象稱為 目標管道物件,會執行端點相關工作。 本文說明如何使用目標管道物件取得管道資訊。

對於 Windows 驅動程式模型 (WDM) 用戶端驅動程式,USB 驅動程式堆疊會傳回 USBD_PIPE_INFORMATION 結構的數位。 陣列中的元素數目取決於針對所選組態中介面的作用中替代設定所定義的端點數目。 每個元素都包含針對特定端點所建立管道的相關信息。 如需選取組態並取得管道資訊陣列的資訊,請參閱 如何選取USB裝置的設定。

您所需了解的事情

在客戶端驅動程式可以列舉管道之前,請確定符合這些需求:

  • 用戶端驅動程序必須已建立架構 USB 目標裝置物件。

    如果您使用 Microsoft Visual Studio Professional 2012 提供的 USB 範本,範本程式代碼會執行這些工作。 範本程式代碼會取得目標裝置物件的句柄,並儲存在裝置內容中。

    KMDF 用戶端驅動程式:

    KMDF 用戶端驅動程式必須藉由呼叫 WdfUsbTargetDeviceCreateWithParameters 方法來取得 WDFUSBDEVICE 句柄。 如需詳細資訊,請參閱 瞭解USB用戶端驅動程式程式代碼結構 (KMDF) 中的。

    UMDF 用戶端驅動程式:

    UMDF 用戶端驅動程序必須藉由查詢架構目標裝置物件來取得 IWDFUsbTargetDevice 指標。 For more information, see "IPnpCallbackHardware implementation and USB-specific tasks" in Understanding the USB client driver code structure (UMDF).

  • 裝置必須具有作用中的設定。

    如果您使用 USB 範本,程式代碼會選取每個介面中的第一個組態和預設替代設定。 如需如何變更該預設設定的資訊,請參閱 如何在USB介面中選取替代設定

    KMDF 用戶端驅動程式:

    KMDF 用戶端驅動程序必須呼叫 WdfUsbTargetDeviceSelectConfig 方法。

    UMDF 用戶端驅動程式:

    針對UMDF用戶端驅動程式,架構會選取該組態中每個介面的第一個組態和預設替代設定。

在 KMDF 用戶端驅動程式中取得 USB 管道句柄

架構代表USB驅動程式堆疊所開啟的每個管道,做為USB目標管道物件。 KMDF 用戶端驅動程式可以存取目標管道物件的方法,以取得管道的相關信息。 若要執行數據傳輸,用戶端驅動程序必須具有WDFUSBPIPE管道句柄。 若要取得管道句柄,驅動程式必須列舉使用中組態的介面和替代設定,然後列舉每個設定中定義的端點。 針對每個數據傳輸執行列舉作業可能很昂貴。 因此,其中一種方法是在設定裝置之後取得管道句柄,並將其儲存在驅動程式定義的裝置內容中。 當驅動程式收到數據傳輸要求時,驅動程式可以從裝置內容擷取所需的管道句柄,並使用它們來傳送要求。 例如,如果客戶端驅動程式變更裝置的設定,則驅動程式也必須使用新的管道控點重新整理裝置內容。 否則,驅動程式可能會錯誤地在過時管道句柄上傳送傳送要求。

控制傳輸不需要管道控點。 若要傳送控制傳輸要求,WDF 用戶端驅動程式會呼叫架構裝置物件所公開的 WdfUsbDevicexxxx 方法。 這些方法需要 WDFUSBDEVICE 句柄,才能起始以預設端點為目標的控制傳輸。 針對這類傳輸,要求的 I/O 目標是預設端點,並由 WDFIOTARGET 句柄表示,WDFUSBPIPE 句柄會抽象化。 在裝置層級,WDFUSBDEVICE 句柄是預設端點 WDFUSBPIPE 句柄的抽象概念。

如需傳送控制傳輸和 KMDF 方法的相關信息,請參閱 如何傳送 USB 控制項傳輸

  1. 擴充裝置內容結構以儲存管道句柄。

    如果您知道裝置中的端點,請新增WDFUSBPIPE成員來儲存相關聯的USB管道句柄,以擴充您的裝置內容結構。 例如,您可以擴充裝置內容結構,如下所示:

    typedef struct _DEVICE_CONTEXT {
        WDFUSBDEVICE    UsbDevice;
        WDFUSBINTERFACE UsbInterface;
        WDFUSBPIPE      BulkReadPipe;   // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      BulkWritePipe;  // Pipe opened for the bulk IN endpoint.
        WDFUSBPIPE      InterruptPipe;  // Pipe opened for the interrupt IN endpoint.
        WDFUSBPIPE      StreamInPipe;   // Pipe opened for stream IN endpoint.
        WDFUSBPIPE      StreamOutPipe;  // Pipe opened for stream OUT endpoint.
        UCHAR           NumberConfiguredPipes;  // Number of pipes opened.
        ...
        ...                                     // Other members. Not shown.
    
    } DEVICE_CONTEXT, *PDEVICE_CONTEXT;
    
  2. 宣告管道內容結構。

    每個管道都可以將端點相關特性儲存在另一個結構中,稱為 管道內容。 與裝置內容類似,管道內容是由用戶端驅動程式) 所定義的數據結構 (,用來儲存與端點相關聯之管道的相關信息。 在裝置設定期間,客戶端驅動程式會將指標傳遞給其管道內容至架構。 架構會根據結構大小配置記憶體區塊,並使用架構 USB 目標管道物件儲存該記憶體位置的指標。 用戶端驅動程式可以使用指標,在管道內容的成員中存取和儲存管道資訊。

    typedef struct _PIPE_CONTEXT {
    
        ULONG MaxPacketSize;
        ULONG MaxStreamsSupported;
        PUSBD_STREAM_INFORMATION StreamInfo;
    } PIPE_CONTEXT, *PPIPE_CONTEXT;
    
    WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(PIPE_CONTEXT, GetPipeContext)
    
    

    在此範例中,管道內容會儲存可在一個傳輸中傳送的最大位元元組數目。 用戶端驅動程式可以使用該值來判斷傳輸緩衝區的大小。 宣告也包含 WDF_DECLARE_CONTEXT_TYPE_WITH_NAME 宏,其會產生內嵌函式 GetPipeContext。 用戶端驅動程式可以呼叫該函式,以擷取儲存管道內容的記憶體區塊指標。

    如需內容的詳細資訊,請參閱 Framework 對象內容空間

    若要傳遞架構的指標,用戶端驅動程式會先呼叫 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 來初始化其管道內容。 然後,在呼叫 WdfUsbTargetDeviceSelectConfig 時傳遞管道內容的指標, (來選取組態) 或 WdfUsbInterfaceSelectSetting (,以選取替代設定) 。

  3. 裝置設定要求完成之後,請列舉 介面,並取得已設定管道的管道句柄。 您將需要這組資訊:

    • 包含目前設定之介面的 WDFUSBINTERFACE 句柄。 您可以藉由列舉使用中組態中的介面來取得該句柄。 或者,如果您為 WdfUsbTargetDeviceSelectConfig 中的WDF_USB_DEVICE_SELECT_CONFIG_PARAMS結構提供了指標,您可以從單一介面裝置的 Type.SingleInterface.ConfigUsbInterface 成員 (取得) 或 Type.MultiInterface.Pairs.UsbInterface 成員 (的多介面裝置) 句柄。
    • 目前設定中針對端點開啟的管道數目。 您可以藉由呼叫 WdfUsbInterfaceGetNumConfiguredPipes 方法,在特定介面上取得該號碼。
    • 所有已設定管道的 WDFUSBPIPE 句柄。 您可以呼叫 WdfUsbInterfaceGetConfiguredPipe 方法來取得句柄。

    取得管道句柄之後,客戶端驅動程式可以呼叫方法來判斷管道的類型和方向。 驅動程式可以在 WDF_USB_PIPE_INFORMATION 結構中取得端點的相關信息。 驅動程式可以藉由呼叫 WdfUsbTargetPipeGetInformation 方法來取得填入的結構。 或者,驅動程式可以提供 WdfUsbInterfaceGetConfiguredPipe 呼叫中結構的指標。

下列程式代碼範例會列舉目前設定中的管道。 它會取得裝置大量和中斷端點的管道句柄,並將其儲存在驅動程式的裝置內容結構中。 它會將每個端點的封包大小上限儲存在相關聯的管道內容中。 如果端點支持數據流,它會呼叫 OpenStreams 例程來開啟靜態數據流。 OpenStreams 的實作會顯示 在 USB 大量端點中如何開啟和關閉靜態數據流

若要判斷特定大量端點是否支援靜態數據流,用戶端驅動程式會檢查端點描述項。 該程式代碼是在名為 RetrieveStreamInfoFromEndpointDesc 的協助程式例程中實作,如下一個程式代碼區塊所示。

NTSTATUS
    FX3EnumeratePipes(
    _In_ WDFDEVICE Device)

{
    NTSTATUS                    status;
    PDEVICE_CONTEXT             pDeviceContext;
    UCHAR                       i;
    PPIPE_CONTEXT               pipeContext;
    WDFUSBPIPE                  pipe;
    WDF_USB_PIPE_INFORMATION    pipeInfo;

    PAGED_CODE();

    pDeviceContext = GetDeviceContext(Device);

    // Get the number of pipes in the current alternate setting.
    pDeviceContext->NumberConfiguredPipes = WdfUsbInterfaceGetNumConfiguredPipes(
        pDeviceContext->UsbInterface);

    if (pDeviceContext->NumberConfiguredPipes == 0)
    {
        status = USBD_STATUS_BAD_NUMBER_OF_ENDPOINTS;
        goto Exit;
    }
    else
    {
        status = STATUS_SUCCESS;
    }

    // Enumerate the pipes and get pipe information for each pipe.
    for (i = 0; i < pDeviceContext->NumberConfiguredPipes; i++)
    {
        WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);

        pipe =  WdfUsbInterfaceGetConfiguredPipe(
            pDeviceContext->UsbInterface,
            i,
            &pipeInfo);

        if (pipe == NULL)
        {
            continue;
        }

        pipeContext = GetPipeContext (pipe);

        // If the pipe is a bulk endpoint that supports streams,
        // If the host controller supports streams, open streams.
        // Use the endpoint as an IN bulk endpoint.
        // Store the maximum packet size.

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {

            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamInPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamInPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkReadPipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeBulk == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsOutEndpoint (pipe))
        {
            // Check if this is a streams IN endpoint. If it is,
            // Get the maximum number of streams and store
            // the value in the pipe context.
            RetrieveStreamInfoFromEndpointDesc (
                Device,
                pipe);

            if ((pipeContext->IsStreamsCapable) &&
                (pipeContext->MaxStreamsSupported > 0))
            {
                status = OpenStreams (
                    Device,
                    pipe);

                if (status != STATUS_SUCCESS)
                {
                    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                        "%!FUNC! Could not open streams.");

                    pDeviceContext->StreamOutPipe = NULL;
                }
                else
                {
                    pDeviceContext->StreamOutPipe = pipe;
                    pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
                }
            }
            else
            {
                pDeviceContext->BulkWritePipe = pipe;
                pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            }

            continue;
        }

        if ((WdfUsbPipeTypeInterrupt == pipeInfo.PipeType) &&
            WdfUsbTargetPipeIsInEndpoint (pipe))
        {
            pDeviceContext->InterruptPipe = pipe;
            pipeContext->MaxPacketSize = pipeInfo.MaximumPacketSize;
            continue;
        }

    }

Exit:
    return status;
}

下列程式代碼範例顯示用戶端驅動程式在列舉管道時呼叫的 Helper 例程 RetrieveStreamInfoFromEndpointDesc。

在下列程式代碼範例中,用戶端驅動程式會在列舉管道時呼叫上述協助程式例程 RetrieveStreamInfoFromEndpointDesc。 例程會先檢查組態描述項,並剖析它以擷取端點描述元。 如果管道的端點描述項包含USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE描述元,驅動程式會擷取端點支援的最大數據流數目。

/*++
Routine Description:

This routine parses the configuration descriptor and finds the endpoint
with which the specified pipe is associated.
It then retrieves the maximum number of streams supported by the endpoint.
It stores maximum number of streams in the pipe context.

Arguments:

Device - WDFUSBDEVICE handle to the target device object.
The driver obtained that handle in a previous call to
WdfUsbTargetDeviceCreateWithParameters.

Pipe - WDFUSBPIPE handle to the target pipe object.

Return Value:

NTSTATUS
++*/

VOID RetrieveStreamInfoFromEndpointDesc (
    WDFDEVICE Device,
    WDFUSBPIPE Pipe)
{
    PDEVICE_CONTEXT                                 deviceContext                = NULL;
    PUSB_CONFIGURATION_DESCRIPTOR                   configDescriptor             = NULL;
    WDF_USB_PIPE_INFORMATION                        pipeInfo;
    PUSB_COMMON_DESCRIPTOR                          pCommonDescriptorHeader      = NULL;
    PUSB_INTERFACE_DESCRIPTOR                       pInterfaceDescriptor         = NULL;
    PUSB_ENDPOINT_DESCRIPTOR                        pEndpointDescriptor          = NULL;
    PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR   pEndpointCompanionDescriptor = NULL;
    ULONG                                           maxStreams;
    ULONG                                           index;
    BOOLEAN                                         found                        = FALSE;
    UCHAR                                           interfaceNumber = 0;
    UCHAR                                           alternateSetting = 1;
    PPIPE_CONTEXT                                   pipeContext = NULL;
    NTSTATUS                                        status;

    PAGED_CODE();

    deviceContext = GetDeviceContext (Device);
    pipeContext = GetPipeContext (Pipe);

    // Get the configuration descriptor of the currently selected configuration
    status = FX3RetrieveConfigurationDescriptor (
        deviceContext->UsbDevice,
        &deviceContext->ConfigurationNumber,
        &configDescriptor);

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not retrieve the configuration descriptor.");

        status = USBD_STATUS_INAVLID_CONFIGURATION_DESCRIPTOR;

        goto Exit;
    }

    if (deviceContext->ConfigurationNumber == 1)
    {
        alternateSetting = 1;
    }
    else
    {
        alternateSetting = 0;
    }

    // Get the Endpoint Address of the pipe
    WDF_USB_PIPE_INFORMATION_INIT(&pipeInfo);
    WdfUsbTargetPipeGetInformation (Pipe, &pipeInfo);

    // Parse the ConfigurationDescriptor (including all Interface and
    // Endpoint Descriptors) and locate a Interface Descriptor which
    // matches the InterfaceNumber, AlternateSetting, InterfaceClass,
    // InterfaceSubClass, and InterfaceProtocol parameters.
    pInterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
        configDescriptor,
        configDescriptor,
        interfaceNumber,  //Interface number is 0.
        alternateSetting,  // Alternate Setting is 1
        -1, // InterfaceClass, ignore
        -1, // InterfaceSubClass, ignore
        -1  // InterfaceProtocol, ignore
        );

    if (pInterfaceDescriptor == NULL )
    {
        // USBD_ParseConfigurationDescriptorEx failed to retrieve Interface Descriptor.
        goto Exit;
    }

    pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR) pInterfaceDescriptor;

    for(index = 0; index < pInterfaceDescriptor->bNumEndpoints; index++)
    {

        pCommonDescriptorHeader = USBD_ParseDescriptors(
            configDescriptor,
            configDescriptor->wTotalLength,
            pCommonDescriptorHeader,
            USB_ENDPOINT_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader == NULL)
        {
            // USBD_ParseDescriptors failed to retrieve Endpoint Descriptor unexpectedly.
            goto Exit;
        }

        pEndpointDescriptor = (PUSB_ENDPOINT_DESCRIPTOR) pCommonDescriptorHeader;

        // Search an Endpoint Descriptor that matches the EndpointAddress
        if (pEndpointDescriptor->bEndpointAddress == pipeInfo.EndpointAddress)
        {

            found = TRUE;
            break;
        }

        // Skip the current Endpoint Descriptor and search for the next.
        pCommonDescriptorHeader = (PUSB_COMMON_DESCRIPTOR)(((PUCHAR)pCommonDescriptorHeader)
            + pCommonDescriptorHeader->bLength);
    }

    if (found)
    {
        // Locate the SuperSpeed Endpoint Companion Descriptor
        // associated with the endpoint descriptor
        pCommonDescriptorHeader = USBD_ParseDescriptors (
            configDescriptor,
            configDescriptor->wTotalLength,
            pEndpointDescriptor,
            USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR_TYPE);

        if (pCommonDescriptorHeader != NULL)
        {
            pEndpointCompanionDescriptor =
                (PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR) pCommonDescriptorHeader;

            maxStreams = pEndpointCompanionDescriptor->bmAttributes.Bulk.MaxStreams;

            if (maxStreams == 0)
            {
                pipeContext->MaxStreamsSupported = 0;
                pipeContext->IsStreamsCapable = FALSE;
            }
            else
            {
                pipeContext->IsStreamsCapable = TRUE;
                pipeContext->MaxStreamsSupported = 1 << maxStreams;
            }
        }
        else
        {
            KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                "USBD_ParseDescriptors failed to retrieve SuperSpeed Endpoint Companion Descriptor unexpectedly.\n" ));
        }
    }
    else
    {
        pipeContext->MaxStreamsSupported = 0;
        pipeContext->IsStreamsCapable = FALSE;
    }

Exit:
    if (configDescriptor)
    {
        ExFreePoolWithTag (configDescriptor, USBCLIENT_TAG);
    }

    return;
}

取得UMDF用戶端驅動程式中的管道控點

UMDF 用戶端驅動程式會使用 COM 基礎結構,並實作與架構裝置物件配對的 COM 回呼類別。 與 KMDF 驅動程式類似,UMDF 用戶端驅動程式只能在裝置設定之後取得管道資訊。 若要取得管道資訊,用戶端驅動程序必須取得包含使用中設定之架構介面物件的 IWDFUsbTargetPipe 介面指標。 藉由使用介面指標,驅動程式可以列舉該設定中的管道,以取得架構目標管道物件所公開的 IWDFUsbTargetPipe 介面指標。

驅動程式開始列舉管道之前,驅動程式必須知道裝置組態和支援的端點。 根據該資訊,驅動程式可以將管道物件儲存為類別成員變數。

下列程式代碼範例會擴充 Visual Studio Professional 2012 提供的 USB UMDF 範本。 For an explanation of the starter code, see "IPnpCallbackHardware implementation and USB-specific tasks" in Understanding the USB client driver code structure (UMDF).

如這裡所示,擴充 CDevice 類別宣告。 此範例程式代碼假設裝置是OSR FX2面板。 如需描述項配置的相關信息,請參閱 USB 裝置配置

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    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;
    IWDFUsbInterface *      m_pIUsbInterface;  //Pointer to the target interface object.
    IWDFUsbTargetPipe *     m_pIUsbInputPipe;  // Pointer to the target pipe object for the bulk IN endpoint.
    IWDFUsbTargetPipe *     m_pIUsbOutputPipe; // Pointer to the target pipe object for the bulk OUT endpoint.
    IWDFUsbTargetPipe *     m_pIUsbInterruptPipe; // Pointer to the target pipe object for the interrupt endpoint.

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
        );

    HRESULT                     // Declare a helper function to enumerate pipes.
    ConfigureUsbPipes(
        );

public:
    // IPnpCallbackHardware methods
    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

在 CDevice 類別定義中,實作名為 CreateUsbIoTargets 的協助程式方法。 在驅動程式取得目標裝置物件的指標之後,會從IPnpCallbackHardware::OnPrepareHardware 實作呼叫此方法。

HRESULT  CMyDevice::CreateUsbIoTargets()
{
    HRESULT                 hr;
    UCHAR                   NumEndPoints = 0;
    IWDFUsbInterface *      pIUsbInterface = NULL;
    IWDFUsbTargetPipe *     pIUsbPipe = NULL;

    if (SUCCEEDED(hr))
    {
        UCHAR NumInterfaces = pIUsbTargetDevice->GetNumInterfaces();

        WUDF_TEST_DRIVER_ASSERT(1 == NumInterfaces);

        hr = pIUsbTargetDevice->RetrieveUsbInterface(0, &pIUsbInterface);
        if (FAILED(hr))
        {
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Unable to retrieve USB interface from USB Device I/O Target %!HRESULT!",
                        hr
                        );
        }
        else
        {
            m_pIUsbInterface = pIUsbInterface;

            DriverSafeRelease (pIUsbInterface); //release creation reference
        }
     }

    if (SUCCEEDED(hr))
    {
        NumEndPoints = pIUsbInterface->GetNumEndPoints();

        if (NumEndPoints != NUM_OSRUSB_ENDPOINTS)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Has %d endpoints, expected %d, returning %!HRESULT! ",
                        NumEndPoints,
                        NUM_OSRUSB_ENDPOINTS,
                        hr
                        );
        }
    }

    if (SUCCEEDED(hr))
    {
        for (UCHAR PipeIndex = 0; PipeIndex < NumEndPoints; PipeIndex++)
        {
            hr = pIUsbInterface->RetrieveUsbPipeObject(PipeIndex,
                                                  &pIUsbPipe);

            if (FAILED(hr))
            {
                TraceEvents(TRACE_LEVEL_ERROR,
                            TEST_TRACE_DEVICE,
                            "%!FUNC! Unable to retrieve USB Pipe for PipeIndex %d, %!HRESULT!",
                            PipeIndex,
                            hr
                            );
            }
            else
            {
                if ( pIUsbPipe->IsInEndPoint() )
                {
                    if ( UsbdPipeTypeInterrupt == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInterruptPipe = pIUsbPipe;
                    }
                    else if ( UsbdPipeTypeBulk == pIUsbPipe->GetType() )
                    {
                        m_pIUsbInputPipe = pIUsbPipe;
                    }
                    else
                    {
                        pIUsbPipe->DeleteWdfObject();
                    }
                }
                else if ( pIUsbPipe->IsOutEndPoint() && (UsbdPipeTypeBulk == pIUsbPipe->GetType()) )
                {
                    m_pIUsbOutputPipe = pIUsbPipe;
                }
                else
                {
                    pIUsbPipe->DeleteWdfObject();
                }

                DriverSafeRelease(pIUsbPipe);  //release creation reference
            }
        }

        if (NULL == m_pIUsbInputPipe || NULL == m_pIUsbOutputPipe)
        {
            hr = E_UNEXPECTED;
            TraceEvents(TRACE_LEVEL_ERROR,
                        TEST_TRACE_DEVICE,
                        "%!FUNC! Input or output pipe not found, returning %!HRESULT!",
                        hr
                        );
        }
    }

    return hr;
}

在UMDF中,客戶端驅動程式會使用管道索引來傳送數據傳輸要求。 管道索引是 USB 驅動程式堆疊在設定中開啟端點管道時所指派的數位。 若要取得管道索引,請呼叫**IWDFUsbTargetPipe::GetInformation** 方法。 方法會填入 WINUSB_PIPE_INFORMATION 結構。 PipeId 值表示管道索引。

在目標管道上執行讀取和寫入作業的其中一種方式是呼叫 IWDFUsbInterface::GetWinUsbHandle 以取得 WinUSB 句柄,然後呼叫 WinUSB 函式。 例如,驅動程式可以呼叫 WinUsb_ReadPipeWinUsb_WritePipe 函式。 在這些函式調用中,驅動程式必須指定管道索引。 如需詳細資訊,請參閱 如何使用 WinUSB 函式存取 USB 裝置

WDM 型用戶端驅動程式的管道控點

選取設定之後,USB 驅動程式堆疊會設定每個裝置端點的管道。 USB 驅動程式堆疊會傳回 USBD_PIPE_INFORMATION 結構的數位。 陣列中的元素數目取決於針對所選組態中介面的作用中替代設定所定義的端點數目。 每個元素都包含針對特定端點所建立管道的相關信息。 如需取得管道控點的詳細資訊,請參閱 如何選取USB裝置的設定。

若要建置 I/O 傳輸要求,用戶端驅動程式必須具有與該端點相關聯的管道句柄。 用戶端驅動程式可以從數位中USBD_PIPE_INFORMATIONPipeHandle 成員取得管道句柄。

除了管道句柄之外,客戶端驅動程式也需要管道類型。 用戶端驅動程式可以檢查 PipeType 成員來判斷管道類型。

根據端點類型,USB 驅動程式堆疊支援不同類型的管道。 用戶端驅動程式可以檢查 USBD_PIPE_INFORMATIONPipeType 成員來判斷管道類型。 不同的管道類型需要不同類型的USB要求區塊, (URI) 來執行I/O交易。

然後,客戶端驅動程式會將 URB 提交至 USB 驅動程式堆疊。 USB 驅動程式堆疊會處理要求,並將指定的數據傳送至要求的目標管道。

URB 包含要求的相關信息,例如目標管道句柄、傳輸緩衝區及其長度。 URB 等位內的每個結構都會共用特定成員:TransferFlagsTransferBuffer、TransferBufferLengthTransferBufferMDL TransferFlags 成員中有對應至每個 URB 類型的類型特定旗標。 對於所有數據傳輸 URB,TransferFlags 中的USBD_TRANSFER_DIRECTION_IN旗標會指定傳輸的方向。 用戶端驅動程式會將USBD_TRANSFER_DIRECTION_IN旗標設定為從裝置讀取數據。 驅動程式會清除此旗標,以將數據傳送至裝置。 數據可以從記憶體或 MDL 中的緩衝區讀取或寫入。 在這兩種情況下,驅動程式會指定 TransferBufferLength 成員中的緩衝區大小。 驅動程式會在 TransferBuffer 成員中提供駐留緩衝區,以及在 TransferBufferMDL 成員中提供 MDL。 無論驅動程式提供哪一個,另一個必須是 NULL。