共用方式為


瞭解 USB 用戶端驅動程式程式代碼結構 (UMDF)

在本主題中,您將瞭解UMDF型USB用戶端驅動程式的原始碼。 程式代碼範例是由visual Studio Microsoft隨附的 USB使用者模式驅動程式 範本所產生。 範本程式代碼會使用 Active Template Library (ATL) 來產生 COM 基礎結構。 這裡不會討論 ATL 和用戶端驅動程式中 COM 實作的詳細數據。

如需產生 UMDF 範本程式代碼的指示,請參閱如何撰寫您的第一個 USB 用戶端驅動程式 (UMDF)。 這些章節將討論範本程式代碼:

在討論範本程式代碼的詳細數據之前,讓我們看看頭檔 (Internal.h) 中與 UMDF 驅動程式開發相關的一些宣告。

Internal.h 包含這些檔案,包含在 Windows 驅動程式套件中(WDK):

#include "atlbase.h"
#include "atlcom.h"

#include "wudfddi.h"
#include "wudfusb.h"

Atlbase.h 和 atlcom.h 包含 ATL 支援的宣告。 用戶端驅動程式所實作的每個類別都會實作 ATL 類別公用 CComObjectRootEx。

UMDF 驅動程式開發中總是會包含 Wudfddi.h。 標頭檔包含編譯 UMDF 驅動程式所需的各種方法和結構宣告和定義。

Wudfusb.h 包含與架構所提供的 USB I/O 目標對象通訊所需的 UMDF 結構和方法的宣告和定義。

Internal.h 中的下一個區塊會宣告裝置介面的 GUID 常數。 應用程式可以使用這個 GUID,以透過 SetupDiXxx API 開啟裝置的控制代碼。 GUID 會在架構建立裝置對象之後註冊。

// Device Interface GUID
// f74570e5-ed0c-4230-a7a5-a56264465548

DEFINE_GUID(GUID_DEVINTERFACE_MyUSBDriver_UMDF_,
    0xf74570e5,0xed0c,0x4230,0xa7,0xa5,0xa5,0x62,0x64,0x46,0x55,0x48);

下一個部分會宣告追蹤巨集和追蹤 GUID。 請注意追蹤 GUID,您需要它才能啟用追蹤。

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        MyDriver1TraceGuid, (f0261b19,c295,4a92,aa8e,c6316c82cdf0),    \
                                                                       \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)                              \
        WPP_DEFINE_BIT(TRACE_DRIVER)                                   \
        WPP_DEFINE_BIT(TRACE_DEVICE)                                   \
        WPP_DEFINE_BIT(TRACE_QUEUE)                                    \
        )                             

#define WPP_FLAG_LEVEL_LOGGER(flag, level)                             \
    WPP_LEVEL_LOGGER(flag)

#define WPP_FLAG_LEVEL_ENABLED(flag, level)                            \
    (WPP_LEVEL_ENABLED(flag) &&                                        \
     WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)

#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
           WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
           (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

Internal.h forward 中的下一行會宣告佇列回呼物件的用戶端驅動程序實作類別。 它也包含範本所產生的其他項目檔。 本主題稍後會討論實作和項目頭檔。

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

#include "Driver.h"
#include "Device.h"
#include "IoQueue.h"

安裝客戶端驅動程序之後,Windows 會在主機進程的實例中載入客戶端驅動程式和架構。 從這裡,架構會載入並初始化用戶端驅動程式。 架構會執行下列工作:

  1. 在架構中建立 驅動程序物件 ,此物件代表您的用戶端驅動程式。
  2. 從類別工廠要求 IDriverEntry 介面指標。
  3. 在架構中建立 裝置物件
  4. 在 PnP 管理員啟動裝置之後,初始化裝置物件。

當驅動程式載入和初始化時,會發生數個事件,而架構可讓用戶端驅動程式參與處理它們。 在用戶端驅動程式端,驅動程式會執行下列工作:

  1. 實作並匯出來自用戶端驅動程式模組的 DllGetClassObject 函式,讓架構可以取得驅動程序的參考。
  2. 提供實作 IDriverEntry 介面的回呼類別。
  3. 提供實作 IPnpCallbackXxx 介面的回呼類別。
  4. 取得裝置對象的參考,並根據用戶端驅動程式的需求進行設定。

驅動程式回呼原始程式碼

架構會建立 驅動程序物件,此物件代表 Windows 所載入之用戶端驅動程式的實例。 用戶端驅動程式至少提供一個回呼函式,將驅動程式註冊到架構中。

驅動程式回呼的完整原始碼位於 Driver.h 和 Driver.c 中。

用戶端驅動程序必須定義實作 IUnknownIDriverEntry 介面的驅動程式回呼類別。 頭檔 Driver.h 會宣告名為 CMyDriver 的類別,其會定義驅動程式回呼。

EXTERN_C const CLSID CLSID_Driver;

class CMyDriver :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CMyDriver, &CLSID_Driver>,
    public IDriverEntry
{
public:

    CMyDriver()
    {
    }

    DECLARE_NO_REGISTRY()

    DECLARE_NOT_AGGREGATABLE(CMyDriver)

    BEGIN_COM_MAP(CMyDriver)
        COM_INTERFACE_ENTRY(IDriverEntry)
    END_COM_MAP()

public:

    // IDriverEntry methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnInitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return S_OK;
    }

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnDeviceAdd(
        __in IWDFDriver *FxWdfDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeinitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return;
    }

};

OBJECT_ENTRY_AUTO(CLSID_Driver, CMyDriver)

驅動程式回呼必須是 COM 類別,這表示它必須實作 IUnknown 和相關方法。 在範本程式代碼中,ATL 類別 CComObjectRootEx 和 CComCoClass 包含 IUnknown 方法。

在 Windows 具現化主機進程之後,架構會建立驅動程序物件。 若要這樣做,架構會建立驅動程式回呼類別的實例,並呼叫 DllGetClassObject 的驅動程式實作(在 驅動程式專案原始程式碼 一節中討論),並取得客戶端驅動程式的 IDriverEntry 介面指標。 該呼叫會將驅動程式回呼物件註冊到架構驅動程式物件中。 成功註冊時,架構會在發生特定驅動程式特定事件時叫用用戶端驅動程序的實作。 架構叫用的第一個方法是 IDriverEntry::OnInitialize 方法。 在客戶端驅動程式的 IDriverEntry::OnInitialize 實作中,用戶端驅動程式可以配置全域驅動程序資源。 這些資源必須在架構準備卸除用戶端驅動程式之前,由架構呼叫的 IDriverEntry::OnDeinitialize 中釋出。 範本程式代碼提供 OnInitializeOnDeinitialize 方法的最小實作。

IDriverEntry 最重要的方法是 IDriverEntry::OnDeviceAdd。 在架構建立架構裝置物件之前(在下一節中討論),它會呼叫驅動程式的 IDriverEntry::OnDeviceAdd 實作 。 呼叫 方法時,架構會將 IWDFDriver 指標傳遞至驅動程式物件和 IWDFDeviceInitialize 指標。 用戶端驅動程式可以呼叫 IWDFDeviceInitialize 方法來指定特定組態選項。

用戶端驅動程式通常會在其 IDriverEntry::OnDeviceAdd 實作中執行下列工作:

  • 指定要建立之裝置物件的組態資訊。
  • 初始化驅動程式的裝置回呼類別。
  • 建立框架裝置物件,並將其裝置回呼物件註冊於框架中。
  • 初始化架構裝置物件。
  • 註冊客戶端驅動程式的裝置介面 GUID。

在範本程式代碼中, IDriverEntry::OnDeviceAdd 會呼叫在裝置回呼類別中定義的靜態方法 CMyDevice::CreateInstanceAndInitialize。 靜態方法會先具現化用戶端驅動程式的裝置回呼類別,然後建立架構裝置物件。 裝置回呼類別也會定義名為 Configure 的公用方法,以執行上述清單中的剩餘工作。 下一節將討論裝置回呼類別的實作。 下列程式代碼範例顯示範本程式代碼中的 IDriverEntry::OnDeviceAdd 實作。

HRESULT
CMyDriver::OnDeviceAdd(
    __in IWDFDriver *FxWdfDriver,
    __in IWDFDeviceInitialize *FxDeviceInit
    )
{
    HRESULT hr = S_OK;
    CMyDevice *device = NULL;

    hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
                                                FxDeviceInit,
                                                &device);

    if (SUCCEEDED(hr))
    {
        hr = device->Configure();
    }

    return hr;
}

下列程式代碼範例顯示 Device.h 中的裝置類別宣告。

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;

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

    // IPnpCallbackHardware methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

裝置回呼原始程式碼

架構裝置對像是架構類別的實例,代表載入客戶端驅動程式之裝置堆疊中的裝置物件。 如需裝置物件功能的相關信息,請參閱 裝置節點和裝置堆疊

裝置物件的完整原始碼位於 Device.h 和 Device.c。

架構裝置類別會實作 IWDFDevice 介面。 用戶端驅動程式負責在驅動程式 IDriverEntry::OnDeviceAdd 的實作中建立該類別的實例。 建立對象之後,客戶端驅動程式會取得新物件的 IWDFDevice 指標,並呼叫該介面上的方法來管理裝置對象的作業。

IDriverEntry::OnDeviceAdd 實作

在上一節中,您簡短地看到用戶端驅動程式在 IDriverEntry::OnDeviceAdd 中執行的工作。 以下是這些工作的詳細資訊。 用戶端驅動程式:

  • 指定要建立之裝置物件的組態資訊。

    在用戶端驅動程序實作 IDriverEntry::OnDeviceAdd 方法的架構呼叫中,架構會傳遞 IWDFDeviceInitialize 指標。 用戶端驅動程式會使用此指標來指定要建立之裝置物件的組態資訊。 例如,客戶端驅動程式會指定客戶端驅動程式是篩選條件還是函式驅動程式。 若要將用戶端驅動程序識別為篩選驅動程式,它會呼叫 IWDFDeviceInitialize::SetFilter。 在此情況下,架構會建立篩選裝置物件 (FiDO):否則,會建立函式裝置物件 (FDO)。 您可以設定的另一個選項是呼叫 IWDFDeviceInitialize::SetLockingConstraint 來設定同步處理模式。

  • 呼叫 IWDFDriver::CreateDevice 方法,並傳遞 IWDFDeviceInitialize 介面指標、裝置回呼物件的 IUnknown 參考,以及指標至指標的 IWDFDevice 變數。

    如果 IWDFDriver::CreateDevice 呼叫成功:

    • 架構會建立裝置物件。

    • 架構會向架構註冊裝置回呼。

      將裝置回呼與架構裝置物件配對之後,架構和客戶端驅動程式會處理某些事件,例如 PnP 狀態和電源狀態變更。 例如,當 PnP 管理員啟動裝置時,就會通知架構。 架構接著調用裝置回呼的 IPnpCallbackHardware::OnPrepareHardware 實作。 每個客戶端驅動程式都必須註冊至少一個裝置回呼物件。

    • 用戶端驅動程式會接收 IWDFDevice 變數中新裝置物件的位址。 收到架構裝置物件的指標時,用戶端驅動程式可以繼續進行初始化工作,例如設定 I/O 流程的佇列,以及註冊裝置介面 GUID。

  • 呼叫 IWDFDevice::CreateDeviceInterface 來註冊用戶端驅動程式的裝置介面 GUID。 應用程式可以使用 GUID 將要求傳送至客戶端驅動程式。 GUID 常數會在 Internal.h 中宣告。

  • 初始化從裝置往返 I/O 傳輸的佇列。

範本程式代碼會定義 Helper 方法 Initialize,這個方法會指定組態資訊並建立裝置物件。

下列程式代碼範例顯示 Initialize 的實作。

HRESULT
CMyDevice::Initialize(
    __in IWDFDriver           * FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    )
{
    IWDFDevice *fxDevice = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    FxDeviceInit->SetLockingConstraint(None);

    FxDeviceInit->SetPowerPolicyOwnership(TRUE);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get IUnknown %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
    DriverSafeRelease(unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create a framework device %!hresult!",
                    hr);
        goto Exit;
    }

     m_FxDevice = fxDevice;

     DriverSafeRelease(fxDevice);

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

在上述程式代碼範例中,用戶端驅動程式會建立裝置物件並註冊其裝置回呼。 建立裝置物件之前,驅動程式會在 IWDFDeviceInitialize 介面指標上呼叫方法,以指定其組態喜好設定。 這是架構在先前呼叫用戶端驅動程式 IDriverEntry::OnDeviceAdd 方法時傳遞的相同指標。

用戶端驅動程式會指定它將成為裝置對象的電源原則擁有者。 作為電源原則擁有者,用戶端驅動程式會決定裝置在系統電源狀態變更時應輸入的適當電源狀態。 驅動程式也會負責將相關要求傳送至裝置,以便進行電源狀態轉換。 根據預設,以 UMDF 為基礎的用戶端驅動程式不是電源原則擁有者;架構會處理所有電源狀態轉換。 當系統進入睡眠狀態時,架構會自動將裝置傳送至 D3,而當系統進入 S0 工作狀態時,會將裝置帶回 D0。 如需詳細資訊,請參閱 UMDF 中的電源策略所有權

另一個組態選項是指定客戶端驅動程式是篩選驅動程式或裝置的函式驅動程式。 請注意,在程式代碼範例中,客戶端驅動程式不會明確指定其喜好設定。 這表示客戶端驅動程式是函式驅動程式,架構應該在裝置堆疊中建立 FDO。 如果客戶端驅動程式想要成為篩選驅動程式,則驅動程式必須呼叫 IWDFDeviceInitialize::SetFilter 方法。 在此情況下,架構會在裝置堆疊中建立 FiDO。

用戶端驅動程式還指出,架構進行的所有對用戶端驅動程式回呼的呼叫都不會被同步。 用戶端驅動程式會處理所有同步處理工作。 若要指定該喜好設定,用戶端驅動程式會呼叫 IWDFDeviceInitialize::SetLockingConstraint 方法。

接下來,客戶端驅動程式會呼叫 IUnknown::QueryInterface,以取得其裝置回呼類別的 IUnknown 指標。 接著,用戶端驅動程式會呼叫 IWDFDriver::CreateDevice,此物件會建立架構裝置物件,並使用 IUnknown 指標註冊客戶端驅動程式的裝置回呼。

請注意,用戶端驅動程式會將裝置對象的位址(透過 IWDFDriver::CreateDevice 呼叫接收)儲存在裝置回呼類別的私人數據成員中,然後藉由呼叫 DriverSafeRelease 來釋放該參考(內部.h 中定義的內嵌函式)。 這是因為裝置物件的存留期是由架構追蹤。 因此,客戶端驅動程式不需要保留裝置物件的其他參考計數。

範本程式代碼會定義 Public 方法 Configure,以註冊裝置介面 GUID 並設定佇列。 下列程式代碼範例顯示裝置回呼類別 CMyDevice 中 Configure 方法的定義。 建立架構裝置對象之後, IDriverEntry::OnDeviceAdd 會呼叫 Configure。

CMyDevice::Configure(
    VOID
    )
{

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

     hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create and initialize queue %!hresult!",
                    hr);
        goto Exit;
    }

    hr = m_IoQueue->Configure();
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to configure queue %!hresult!",
                    hr);
        goto Exit;
    } 

    hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_MyUSBDriver_UMDF_,NULL);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create device interface %!hresult!",
                    hr);
        goto Exit;
    }

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

在上述程式代碼範例中,用戶端驅動程式會執行兩個主要工作:初始化 I/O 流程的佇列,並註冊裝置介面 GUID。

佇列會在 CMyIoQueue 類別中建立和設定。 第一個工作是呼叫名為 CreateInstanceAndInitialize 的靜態方法,來具現化該類別。 用戶端驅動程式會呼叫 Configure 來初始化佇列。 CreateInstanceAndInitialize 和 Configure 會在 CMyIoQueue 中宣告,本主題稍後會討論。

用戶端驅動程式也會呼叫 IWDFDevice::CreateDeviceInterface 來註冊客戶端驅動程式的裝置介面 GUID。 應用程式可以使用 GUID 將要求傳送至客戶端驅動程式。 GUID 常數會在 Internal.h 中宣告。

IPnpCallbackHardware 實作和 USB 特定工作

接下來,讓我們看看 Device.cpp 中 IPnpCallbackHardware 介面的實作。

每個裝置回呼類別都必須實作 IPnpCallbackHardware 介面。 此介面有兩種方法: IPnpCallbackHardware::OnPrepareHardwareIPnpCallbackHardware::OnReleaseHardware。 架構會呼叫這些方法,以回應兩個事件:當 PnP 管理員啟動裝置以及移除裝置時。 啟動裝置時,會建立與硬體的通訊,但裝置尚未進入工作狀態 (D0)。 因此,在 IPnpCallbackHardware::OnPrepareHardware 中,用戶端驅動程式可以從硬體取得裝置資訊、配置資源,以及初始化驅動程式存留期間所需的架構物件。 當 PnP 管理員移除裝置時,驅動程式會從系統卸除。 架構會呼叫用戶端驅動程式的 IPnpCallbackHardware::OnReleaseHardware 實作,驅動程式可以在其中釋放這些資源和架構物件。

PnP 管理員可以產生來自 PnP 狀態變更的其他事件類型。 架構會提供這些事件的默認處理。 用戶端驅動程式可以選擇參與這些事件的處理。 請考慮從主機卸離 USB 裝置的情況。 PnP 管理員會辨識該事件並通知架構。 如果客戶端驅動程式想要執行其他工作以回應事件,驅動程式必須在裝置回呼類別中實作 IPnpCallback 介面和相關 IPnpCallback::OnSurpriseRemoval 方法。 否則,架構會繼續進行其事件的默認處理。

USB 用戶端驅動程序必須擷取所支援介面、替代設定和端點的相關信息,並在傳送任何 I/O 要求進行數據傳輸之前進行設定。 UMDF 提供特製化的 I/O 目標物件,可簡化用戶端驅動程式的許多設定工作。 若要設定 USB 裝置,用戶端驅動程式需要只有在 PnP 管理員啟動裝置之後才能使用的裝置資訊。

此範本程式代碼會在 IPnpCallbackHardware::OnPrepareHardware 方法中建立這些物件。

一般而言,客戶端驅動程式會執行其中一或多個設定工作(視裝置的設計而定):

  1. 擷取目前組態的相關信息,例如介面數目。 架構會選取 USB 裝置上的第一個設定。 在多重設定裝置的情況下,用戶端驅動程式無法選取另一個組態。
  2. 擷取介面的相關信息,例如端點數目。
  3. 如果介面支援多個設定,則變更每個介面內的替代設定。 根據預設,架構會選取USB裝置上第一個組態中每個介面的第一個替代設定。 用戶端驅動程式可以選擇選取替代設定。
  4. 擷取每個介面內端點的相關信息。

若要執行這些工作,用戶端驅動程式可以使用WDF所提供的這些特殊USB I/O目標物件類型。

USB I/O 目標物件 說明 UMDF 介面
目標裝置物件 代表USB裝置,並提供方法來擷取裝置描述元,並將控制要求傳送至裝置。 IWDFUsbTargetDevice
目標介面物件 表示個別介面,並提供客戶端驅動程式可以呼叫的方法,以選取替代設定並擷取設定的相關信息。 IWDFUsbInterface
目標管道物件 表示在當前替代設定中配置的介面端點的個別管道。 USB 總線驅動程式會選取所選配置中的每個介面,並為介面內的每個端點設置通信通道。 在USB術語中,該通道稱為 管道 IWDFUsbTargetPipe

下列程式代碼範例示範 IPnpCallbackHardware::OnPrepareHardware 的實作。

HRESULT
CMyDevice::OnPrepareHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    HRESULT hr;
    IWDFUsbTargetFactory *usbFactory = NULL;
    IWDFUsbTargetDevice *usbDevice = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&usbFactory));

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get USB target factory %!hresult!",
                    hr);
        goto Exit;
    }

    hr = usbFactory->CreateUsbTargetDevice(&usbDevice);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create USB target device %!hresult!",
                    hr);

        goto Exit;
    }

     m_FxUsbDevice = usbDevice;

Exit:

    DriverSafeRelease(usbDevice);

    DriverSafeRelease(usbFactory);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

若要使用架構的 USB I/O 目標物件,用戶端驅動程式必須先建立 USB 目標裝置物件。 在架構物件模型中,USB 目標裝置對像是代表 USB 裝置之裝置物件的子系。 USB 目標裝置物件是由架構實作,並執行 USB 裝置的所有裝置層級工作,例如選取設定。

在上述程式代碼範例中,用戶端驅動程式會查詢架構裝置物件,並取得類別工廠的 IWDFUsbTargetFactory 指標,以建立 USB 目標裝置對象。 用戶端驅動程式會使用該指標呼叫 IWDFUsbTargetDevice::CreateUsbTargetDevice 方法。 方法會建立 USB 目標裝置物件,並傳回 IWDFUsbTargetDevice 介面的指標。 方法也會選取預設的(第一個)組態,以及該組態中每個介面的替代設定 0。

範本程式代碼會將USB目標裝置物件的位址(透過 IWDFDriver::CreateDevice 呼叫接收)儲存在裝置回呼類別的私人數據成員中,然後藉由呼叫 DriverSafeRelease 釋放該參考。 框架會維護 USB 目標裝置物件的參考計數。 只要裝置對象處於運作狀態,物件即為運作狀態。 用戶端驅動程式必須在 IPnpCallbackHardware::OnReleaseHardware 中釋放參考。

用戶端驅動程式建立 USB 目標裝置對象之後,驅動程式會呼叫 IWDFUsbTargetDevice 方法來執行下列工作:

  • 擷取裝置、組態、介面描述元和其他資訊,例如裝置速度。
  • 將 I/O 控制件要求格式化並傳送至預設端點。
  • 設定整個 USB 裝置的電源原則。

如需詳細資訊,請參閱 在 UMDF 中使用 USB 裝置。 下列程式代碼範例示範 IPnpCallbackHardware::OnReleaseHardware 的實作。

HRESULT
CMyDevice::OnReleaseHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    if (m_FxUsbDevice != NULL) {

        m_FxUsbDevice->DeleteWdfObject();
        m_FxUsbDevice = NULL;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return S_OK;
}

佇列原始程式碼

架構 佇列物件 代表特定架構裝置物件的 I/O 佇列。 佇列物件的完整原始碼位於IoQueue.h和IoQueue.c中。

IoQueue.h

標頭檔案 IoQueue.h 宣告佇列回呼類別。

class CMyIoQueue :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IQueueCallbackDeviceIoControl
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyIoQueue)

    BEGIN_COM_MAP(CMyIoQueue)
        COM_INTERFACE_ENTRY(IQueueCallbackDeviceIoControl)
    END_COM_MAP()

    CMyIoQueue() : 
        m_FxQueue(NULL),
        m_Device(NULL)
    {
    }

    ~CMyIoQueue()
    {
        // empty
    }

    HRESULT
    Initialize(
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice
        );

    static 
    HRESULT 
    CreateInstanceAndInitialize( 
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice,
        __out CMyIoQueue**    Queue
        );

    HRESULT
    Configure(
        VOID
        )
    {
        return S_OK;
    }


    // IQueueCallbackDeviceIoControl

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeviceIoControl( 
        __in IWDFIoQueue *pWdfQueue,
        __in IWDFIoRequest *pWdfRequest,
        __in ULONG ControlCode,
        __in SIZE_T InputBufferSizeInBytes,
        __in SIZE_T OutputBufferSizeInBytes
        );

private:

    IWDFIoQueue *               m_FxQueue;

    CMyDevice *                 m_Device;

};

在上述程式代碼範例中,用戶端驅動程式會宣告佇列回呼類別。 實例化時,物件會與處理將要求分派至客戶端驅動程式的架構佇列物件合作。 類別會定義兩個方法,以建立和初始化架構佇列物件。 static 方法 CreateInstanceAndInitialize 會具現化佇列回呼類別,然後呼叫 Initialize 方法來建立和初始化架構佇列物件。 它也會指定佇列物件的分派選項。

HRESULT 
CMyIoQueue::CreateInstanceAndInitialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice,
    __out CMyIoQueue** Queue
    )
{

    CComObject<CMyIoQueue> *pMyQueue = NULL;
    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    hr = CComObject<CMyIoQueue>::CreateInstance( &pMyQueue );
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to create instance %!hresult!",
                    hr);
        goto Exit;
    }

    hr = pMyQueue->Initialize(FxDevice, MyDevice);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to initialize %!hresult!",
                    hr);
        goto Exit;
    }

    *Queue = pMyQueue;

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

下列程式代碼範例顯示 Initialize 方法的實作。

HRESULT
CMyIoQueue::Initialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice
    )
{
    IWDFIoQueue *fxQueue = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    assert(FxDevice != NULL);
    assert(MyDevice != NULL);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to query IUnknown interface %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDevice->CreateIoQueue(unknown,
                                 FALSE,     // Default Queue?
                                 WdfIoQueueDispatchParallel,  // Dispatch type
                                 TRUE,     // Power managed?
                                 FALSE,     // Allow zero-length requests?
                                 &fxQueue); // I/O queue
    DriverSafeRelease(unknown);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to create framework queue.");
        goto Exit;
    }

    hr = FxDevice->ConfigureRequestDispatching(fxQueue,
                                               WdfRequestDeviceIoControl,
                                               TRUE);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to configure request dispatching %!hresult!.",
                   hr);
        goto Exit;
    }

    m_FxQueue = fxQueue;
    m_Device= MyDevice;

Exit:

    DriverSafeRelease(fxQueue);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

在上述程式代碼範例中,用戶端驅動程式會建立架構佇列物件。 架構會提供佇列對象來處理客戶端驅動程式的要求流程。

若要建立物件,客戶端驅動程式會在先前透過 IWDFDriver::CreateDevice 取得的 IWDFDevice 參考上呼叫 IWDFDevice::CreateIoQueue

IWDFDevice::CreateIoQueue 呼叫中,用戶端驅動程式會在架構建立佇列之前指定特定組態選項。 這些選項會判斷佇列是否進行電源管理、允許零長度請求,以及作為驅動程式的預設佇列。 用戶端驅動程式會提供這組資訊:

  • 其佇列回呼類別的參考

    指定其佇列回呼類別的 IUnknown 指標。 這會建立架構佇列物件與客戶端驅動程式佇列回呼對象之間的合作關係。 當 I/O 管理員從應用程式收到新的要求時,它會通知架構。 架構接著會使用 IUnknown 指標來叫用佇列回呼對象公開的公用方法。

  • 預設或次要佇列

    佇列必須是預設佇列或次要佇列。 如果架構佇列物件做為預設佇列,則會將所有要求新增至佇列。 次要佇列專用於特定類型的要求。 如果客戶端驅動程式要求次要佇列,則驅動程式也必須呼叫 IWDFDevice::ConfigureRequestDispatching 方法,以指出架構必須放入指定佇列的要求類型。 在範本程式代碼中,用戶端驅動程式會在 bDefaultQueue 參數中傳遞 FALSE。 這會指示 方法建立次要佇列,而不是預設佇列。 它稍後會呼叫 IWDFDevice::ConfigureRequestDispatching ,指出佇列必須只有裝置 I/O 控制要求(請參閱本節中的範例程式代碼)。

  • 分派類型

    佇列物件的分派類型會決定架構如何將要求傳遞至客戶端驅動程式。 傳遞機制可以是循序、平行,或由客戶端驅動程式所定義的自定義機制。 針對循序佇列,在用戶端驅動程式完成先前的要求之前,不會傳遞要求。 在平行分派模式下,架構會在要求從 I/O 管理員送達的瞬間即時轉送。 這表示客戶端驅動程式可以在處理一個要求的同時收到另一個要求。 在自定義機制中,當驅動程式準備好處理它時,用戶端會手動從架構佇列物件提取下一個要求。 在範本程式代碼中,用戶端驅動程式會要求平行分派模式。

  • 具電源管理的佇列

    架構佇列對象必須與裝置的 PnP 和電源狀態同步。 如果裝置不是處於工作狀態,架構佇列物件會停止分派所有要求。 當裝置處於工作狀態時,佇列對象會繼續分派。 在電源管理的佇列中,同步處理是由架構執行;否則,用戶端磁碟驅動器必須處理該工作。 在範本程式代碼中,用戶端會要求電源管理的佇列。

  • 允許長度為零的請求

    用戶端驅動程式可以指示架構使用零長度緩衝區完成 I/O 要求,而不是將它們放入佇列中。 在範本程式代碼中,用戶端會要求架構完成這類要求。

單一架構佇列物件可以處理數種類型的要求,例如讀取、寫入和裝置 I/O 控制件等等。 以範本程式代碼為基礎的用戶端驅動程式只能處理裝置 I/O 控制要求。 為此,客戶端驅動程式的佇列回呼類別會實作 IQueueCallbackDeviceIoControl 介面及其 IQueueCallbackDeviceIoControl::OnDeviceIoControl 方法。 這可讓架構在架構處理裝置 I/O 控件要求時叫用用戶端驅動程式的 IQueueCallbackDeviceIoControl::OnDeviceIoControl 實作。

針對其他類型的要求,客戶端驅動程序必須實作對應的 IQueueCallbackXxx 介面。 例如,如果客戶端驅動程式想要處理讀取要求,佇列回呼類別必須實作 IQueueCallbackRead 介面及其 IQueueCallbackRead::OnRead 方法。 如需了解關於要求和回呼介面類型的資訊,請參閱 I/O 佇列事件回呼函式

下列程式代碼範例顯示 IQueueCallbackDeviceIoControl::OnDeviceIoControl 實作。

VOID
STDMETHODCALLTYPE
CMyIoQueue::OnDeviceIoControl(
    __in IWDFIoQueue *FxQueue,
    __in IWDFIoRequest *FxRequest,
    __in ULONG ControlCode,
    __in SIZE_T InputBufferSizeInBytes,
    __in SIZE_T OutputBufferSizeInBytes
    )
{
    UNREFERENCED_PARAMETER(FxQueue);
    UNREFERENCED_PARAMETER(ControlCode);
    UNREFERENCED_PARAMETER(InputBufferSizeInBytes);
    UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    if (m_Device == NULL) {
        // We don't have pointer to device object
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC!NULL pointer to device object.");
        hr = E_POINTER;
        goto Exit;
    }

    //
    // Process the IOCTLs
    //

Exit:

    FxRequest->Complete(hr);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return;

}

讓我們看看佇列機制的運作方式。 若要與 USB 裝置通訊,應用程式會先開啟裝置的句柄,並使用特定的控制程式代碼呼叫 DeviceIoControl 函式,以傳送裝置 I/O 控件要求。 根據控制項程式代碼的類型,應用程式可以在該呼叫中指定輸入和輸出緩衝區。 呼叫最終會由 I/O 管理員接收,它會通知架構。 架構會建立架構要求物件,並將它新增至架構佇列物件。 在模板代码中,由於佇列物件是使用 WdfIoQueueDispatchParallel 旗標建立的,因此請求新增至佇列時會立即觸發回呼。

當框架喚起用戶端驅動程式的事件回呼時,它會將控制代碼傳遞給保存應用程式所發送請求(及其輸入和輸出緩衝區)的框架請求物件。 此外,它會將控制碼傳送至包含該請求的框架佇列物件。 在事件回呼中,用戶端驅動程序會視需要處理要求。 範本程式代碼只會完成要求。 用戶端驅動程式可以執行更多相關的工作。 例如,如果應用程式要求特定裝置資訊,在事件回呼中,客戶端驅動程式可以建立USB控件要求,並將它傳送至USB驅動程式堆疊,以擷取要求的裝置資訊。 USB 控制要求會在 USB 控制傳輸討論。

驅動程式輸入原始程式碼

在範本程式代碼中,驅動程式入口會在 Dllsup.cpp 中實作。

Dllsup.cpp

在 include 區段之後,會宣告用戶端驅動程式的 GUID 常數。 該 GUID 必須符合驅動程式安裝檔案 (INF) 中的 GUID。

const CLSID CLSID_Driver =
{0x079e211c,0x8a82,0x4c16,{0x96,0xe2,0x2d,0x28,0xcf,0x23,0xb7,0xff}};

下一個程式碼區塊會宣告客戶端驅動程式的類別工廠。

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

範本程式代碼會使用 ATL 支援來封裝複雜的 COM 程式代碼。 類別處理站會繼承範本類別 CAtlDllModuleT,其中包含建立用戶端驅動程式所需的所有程式代碼。

下列代碼段顯示 DllMain 的實作

extern "C"
BOOL
WINAPI
DllMain(
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    )
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        WPP_INIT_TRACING(MYDRIVER_TRACING_ID);

        g_hInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);

    } else if (dwReason == DLL_PROCESS_DETACH) {
        WPP_CLEANUP();
    }

    return _AtlModule.DllMain(dwReason, lpReserved);
}

如果您的用戶端驅動程序實作 DllMain 函式,Windows 會將 DllMain 視為客戶端驅動程式模組的進入點。 在 WUDFHost.exe中載入用戶端驅動程式模組之後,Windows 會呼叫 DllMain 。 Windows 在 Windows 卸除記憶體中的用戶端驅動程式之前,再次呼叫 DllMainDllMain 可以在驅動程式層級配置和釋放全域變數。 在範本程式代碼中,用戶端驅動程式會初始化並釋放 WPP 追蹤所需的資源,並叫用 ATL 類別的 DllMain 實作。

下列代碼段顯示 DllGetClassObject 的實作。

STDAPI
DllGetClassObject(
    __in REFCLSID rclsid,
    __in REFIID riid,
    __deref_out LPVOID FAR* ppv
    )
{
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}

在範本程式代碼中,類別處理站和 DllGetClassObject 會在 ATL 中實作。 上述代碼段只會叫用 ATL DllGetClassObject 實作。 一般而言, DllGetClassObject 必須執行下列工作:

  1. 請確定架構所傳遞的 CLSID 是用戶端驅動程式的 GUID。 架構會從驅動程式的 INF 檔案擷取用戶端驅動程式的 CLSID。 驗證時,請確定指定的 GUID 符合您在 INF 中提供的 GUID。
  2. 具現化用戶端驅動程式所實作的類別處理站。 在範本程式代碼中,這會由ATL類別封裝。
  3. 取得類別工廠 IClassFactory 介面的指標,並將擷取到的指標傳回給架構。

在記憶體中載入客戶端驅動程式模組之後,架構會呼叫驅動程式提供的 DllGetClassObject 函式。 在架構對 DllGetClassObject 的呼叫中,架構會傳遞 CLSID,以識別用戶端驅動程式,並要求取得類別工廠的 IClassFactory 介面的指標。 用戶端驅動程式會實作類別工廠,以利建立驅動程式回呼。 因此,您的用戶端驅動程序必須至少包含一個類別處理站。 架構接著會呼叫 IClassFactory::CreateInstance ,並要求驅動程式回呼類別的 IDriverEntry 指標。

Exports.def

為了讓架構呼叫 DllGetClassObject,用戶端驅動程序必須從 .def 檔案導出函式。 檔案已經包含在Visual Studio專案中。

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

在驅動程式專案隨附的 Export.def 上述代碼段中,用戶端會提供驅動程式模組的名稱做為 LIBRARY,以及 EXPORT 底下的 DllGetClassObject 。 如需詳細資訊,請參閱 使用DEF檔案從 DLL 匯出