瞭解UMDF) (USB用戶端驅動程式程式代碼結構
在本主題中,您將瞭解UMDF型USB用戶端驅動程式的原始碼。 程式代碼範例是由 Microsoft Visual Studio 隨附的 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 會在主機進程的實例中載入客戶端驅動程式和架構。 從這裡開始,架構會載入並初始化用戶端驅動程式。 架構會執行下列工作:
- 在架構中建立 驅動程序物件 ,代表您的用戶端驅動程式。
- 從類別處理站要求 IDriverEntry 介面指標。
- 在架構中建立 裝置物件 。
- 在 PnP 管理員啟動裝置之後,初始化裝置物件。
當驅動程式載入和初始化時,會發生數個事件,而且架構可讓用戶端驅動程式參與處理它們。 在用戶端驅動程式端,驅動程式會執行下列工作:
- 從客戶端驅動程式模組實作及導出 DllGetClassObject 函式,讓架構可以取得驅動程序的參考。
- 提供實作 IDriverEntry 介面的回呼類別。
- 提供實作 IPnpCallbackXxx 介面的回呼類別。
- 取得裝置對象的參考,並根據用戶端驅動程式的需求加以設定。
驅動程式回呼原始程式碼
架構會建立 驅動程序物件,此物件代表 Windows 所載入之用戶端驅動程式的實例。 用戶端驅動程式至少提供一個驅動程式回呼,以向架構註冊驅動程式。
驅動程式回呼的完整原始碼位於 Driver.h 和 Driver.c 中。
用戶端驅動程序必須定義實作 IUnknown 和 IDriverEntry 介面的驅動程式回呼類別。 頭檔 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 介面指標。 該呼叫會向 Framework 驅動程式物件註冊驅動程式回呼物件。 成功註冊時,架構會在發生特定驅動程式特定事件時叫用用戶端驅動程序的實作。 架構所叫用的第一個方法是 IDriverEntry::OnInitialize 方法。 在客戶端驅動程式的 IDriverEntry::OnInitialize 實作中,用戶端驅動程式可以配置全域驅動程序資源。 這些資源必須在 IDriverEntry::OnDeinitialize 中釋出,架構才準備卸除用戶端驅動程式。 範本程式代碼提供 OnInitialize 和 OnDeinitialize 方法的最小實作。
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 來同步處理模式。
傳遞 IWDFDeviceInitialize 介面指標、裝置回呼物件的 IUnknown 參考,以及指針對指標 IWDFDevice 變數,以呼叫 IWDFDriver::CreateDevice 方法。
如果 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 (內部) 中定義的內嵌函式來釋放該參考。 這是因為架構會追蹤裝置物件的存留期。 因此,不需要客戶端驅動程式來保留裝置物件的其他參考計數。
範本程式代碼會定義 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::OnPrepareHardware 和 IPnpCallbackHardware::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 方法中建立這些物件。
一般而言,客戶端驅動程式會根據裝置的設計,執行一或多個這些設定工作 () :
- 擷取目前組態的相關信息,例如介面數目。 架構會選取 USB 裝置上的第一個設定。 用戶端驅動程式無法在多重設定裝置的情況下選取另一個組態。
- 擷取介面的相關信息,例如端點數目。
- 如果介面支援多個設定,請變更每個介面內的替代設定。 根據預設,架構會選取USB裝置上第一個組態中每個介面的第一個替代設定。 用戶端驅動程式可以選擇選取替代設定。
- 擷取每個介面內端點的相關信息。
若要執行這些工作,用戶端驅動程式可以使用WDF所提供的這些特製化USB I/O目標物件類型。
USB I/O 目標物件 | Description | 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 裝置的所有裝置層級工作,例如選取設定。
在上述程式代碼範例中,用戶端驅動程式會查詢架構裝置物件,並取得建立USB目標裝置對象的類別處理站 IWDFUsbTargetFactory 指標。 用戶端驅動程式會使用該指標呼叫 IWDFUsbTargetDevice::CreateUsbTargetDevice 方法。 方法會建立 USB 目標裝置物件,並傳回 IWDFUsbTargetDevice 介面的指標。 方法也會針對該組態中的每個介面選取預設 () 組態和替代設定 0。
範本程式代碼會將透過 IWDFDriver::CreateDevice 呼叫接收的 USB 目標裝置 (物件地址儲存在裝置回呼類別的私人數據成員中) ,然後藉由呼叫 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;
};
在上述程式代碼範例中,用戶端驅動程式會宣告佇列回呼類別。 具現化時,物件會與處理將要求分派至客戶端驅動程式的方式的架構佇列物件合作。 類別會定義兩個方法來建立和初始化架構佇列物件。 靜態方法 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 必須符合驅動程式安裝檔案中的 GUID (INF) 。
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 視為客戶端驅動程式模組的進入點。 Windows 會在載入客戶端驅動程式模組之後呼叫 dllMain WUDFHost.exe。 Windows 在 Windows 卸除記憶體中的用戶端驅動程式之前,再次呼叫 DllMain 。 DllMain 可以在驅動程式層級配置和釋放全域變數。 在範本程式代碼中,用戶端驅動程式會初始化並釋放 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 必須執行下列工作:
- 確定架構所傳遞的 CLSID 是用戶端驅動程式的 GUID。 架構會從驅動程式的 INF 檔案擷取用戶端驅動程式的 CLSID。 驗證時,請確定指定的 GUID 符合您在 INF 中提供的 GUID。
- 具現化用戶端驅動程式所實作的類別處理站。 在範本程式代碼中,這會由ATL類別封裝。
- 取得類別處理站 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 匯出。