瞭解 USB 用戶端驅動程式程式代碼結構 (KMDF)
在本主題中,您將瞭解以 KMDF 為基礎的 USB 用戶端驅動程式的原始程式碼。 程式代碼範例是由visual Studio 2019 Microsoft隨附的USB使用者模式驅動程式範本所產生。
這些區段提供範本程式代碼的相關信息。
如需產生 KMDF 範本程式碼的指示,請參閱 如何撰寫您的第一個 USB 用戶端驅動程式 (KMDF) 。
驅動程式原始碼
驅動程式 物件 代表 Windows 在記憶體中載入驅動程式之後客戶端驅動程式的實例。 驅動程式物件的完整原始碼位於 Driver.h 和 Driver.c 中。
Driver.h
在討論範本程式代碼的詳細數據之前,讓我們看看與 KMDF 驅動程式開發相關的頭檔 (Driver.h) 中的一些宣告。
Driver.h 包含這些檔案,包含在 Windows 驅動程式套件 (WDK) 中。
#include <ntddk.h>
#include <wdf.h>
#include <usb.h>
#include <usbdlib.h>
#include <wdfusb.h>
#include "device.h"
#include "queue.h"
#include "trace.h"
KMDF 驅動程式開發一律會包含 Ntddk.h 和 Wdf.h 頭檔。 標頭檔包含您需要編譯 KMDF 驅動程式之方法和結構的各種宣告和定義。
Usb.h 和 Usbdlib.h 包含用戶端驅動程式針對 USB 裝置所需之結構和例程的宣告和定義。
Wdfusb.h 包含架構所提供 USB I/O 目標對象通訊所需的結構和方法宣告和定義。
Device.h、Queue.h 和 Trace.h 不會包含在 WDK 中。 這些頭檔是由範本產生,稍後會在本主題中討論。
Driver.h 中的下一個區塊會提供 DriverEntry 例程的函式角色類型宣告,以及 EvtDriverDeviceAdd 和 EvtCleanupCallback 事件回呼例程。 驅動程式會實作所有這些例程。 角色類型可協助靜態驅動程序驗證器 (SDV) 分析驅動程式的原始程式碼。 如需角色類型的詳細資訊,請參閱 使用 KMDF 驅動程式的函式角色類型來宣告函式。
DRIVER_INITIALIZE DriverEntry;
EVT_WDF_DRIVER_DEVICE_ADD MyUSBDriver_EvtDeviceAdd;
EVT_WDF_OBJECT_CONTEXT_CLEANUP MyUSBDriver_EvtDriverContextCleanup;
實作檔案 Driver.c 包含下列程式代碼區塊,使用 alloc_text
pragma 來指定 DriverEntry 函式和事件回呼例程是否位於可分頁記憶體中。
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDeviceAdd)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDriverContextCleanup)
#endif
請注意 ,DriverEntry 標示為 INIT,而事件回呼例程則會標示為 PAGE。 INIT 區段指出 DriverEntry 的可執行程式代碼可在驅動程式從 DriverEntry 傳回時分頁並捨棄。 PAGE 區段表示程序代碼不必一直保留在物理記憶體中;它可在未使用時寫入頁面檔案。 如需詳細資訊,請參閱 鎖定可分頁程式代碼或數據。
載入驅動程序之後不久,Windows 會配置代表 驅動程式的DRIVER_OBJECT 結構。 然後它會呼叫驅動程序的進入點例程 DriverEntry,並傳遞結構的指標。 因為 Windows 會依名稱尋找例程,因此每個驅動程式都必須實作名為 DriverEntry 的例程。 例程會執行驅動程式的初始化工作,並將驅動程式的事件回呼例程指定給架構。
下列程式代碼範例顯示範本所產生的 DriverEntry 例程。
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
WDF_DRIVER_CONFIG config;
NTSTATUS status;
WDF_OBJECT_ATTRIBUTES attributes;
//
// Initialize WPP Tracing
//
WPP_INIT_TRACING( DriverObject, RegistryPath );
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Register a cleanup callback so that we can call WPP_CLEANUP when
// the framework driver object is deleted during driver unload.
//
WDF_OBJECT_ATTRIBUTES_INIT(&attributes);
attributes.EvtCleanupCallback = MyUSBDriver_EvtDriverContextCleanup;
WDF_DRIVER_CONFIG_INIT(&config,
MyUSBDriver_EvtDeviceAdd
);
status = WdfDriverCreate(DriverObject,
RegistryPath,
&attributes,
&config,
WDF_NO_HANDLE
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DRIVER, "WdfDriverCreate failed %!STATUS!", status);
WPP_CLEANUP(DriverObject);
return status;
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
DriverEntry 例程有兩個參數:Windows 所配置之DRIVER_OBJECT結構的指標,以及驅動程式的登錄路徑。 RegistryPath 參數代表登錄中的驅動程式特定路徑。
在 DriverEntry 例程中,驅動程式會執行下列工作:
配置驅動程式存留期間所需的全域資源。 例如,在範本程式代碼中,用戶端驅動程式會呼叫 WPP_INIT_TRACING 巨集 來配置 WPP 軟體追蹤所需的資源。
向架構註冊特定事件回呼例程。
若要註冊事件回呼,用戶端驅動程式會先指定特定 WDF 結構中 EvtDriverXxx 例程實作的指標。 然後驅動程式會 呼叫 WdfDriverCreate 方法,並提供這些結構(在下一個步驟中討論)。
呼叫 WdfDriverCreate 方法,並擷取架構驅動程序物件的句柄。
用戶端驅動程式呼叫 WdfDriverCreate 之後,架構會建立架構驅動程式物件來代表用戶端驅動程式。 呼叫完成時,用戶端驅動程式會收到WDFDRIVER句柄,並可擷取驅動程序的相關信息,例如其登錄路徑、版本資訊等等(請參閱 WDF 驅動程式對象參考)。
請注意,架構驅動程式物件與DRIVER_OBJECT所述的 Windows 驅動程式物件不同。 用戶端驅動程式可以隨時使用 WDFDRIVER 句柄並呼叫 WdfGetDriver 方法來取得 WindowsDRIVER_OBJECT 結構的指標。
在 WdfDriverCreate 呼叫之後,架構會與客戶端驅動程式合作以與 Windows 通訊。 架構可作為 Windows 與驅動程式之間的抽象層,並處理大部分複雜的驅動程式工作。 用戶端驅動程式會向架構註冊驅動程式感興趣的事件。 發生特定事件時,Windows 會通知架構。 如果驅動程式為特定事件註冊事件回呼,架構會叫用已註冊的事件回呼來通知驅動程式。 如此一來,驅動程式就有機會視需要處理事件。 如果驅動程式未註冊其事件回呼,架構會繼續進行其事件的默認處理。
驅動程式必須註冊的事件回呼之一是 EvtDriverDeviceAdd。 架構會在架構準備好建立裝置物件時,叫用驅動程式的 EvtDriverDeviceAdd 實作。 在 Windows 中,裝置對像是載入客戶端驅動程式之實體裝置功能的邏輯表示法(本主題稍後討論)。
驅動程式可以註冊的其他事件回呼是 EvtDriverUnload、EvtCleanupCallback 和 EvtDestroyCallback。
在範本程式代碼中,用戶端驅動程式會註冊兩個事件:EvtDriverDeviceAdd 和 EvtCleanupCallback。 驅動程式會指定其實作 EvtDriverDeviceAdd WDF_DRIVER_CONFIG 的指標,以及WDF_OBJECT_ATTRIBUTES結構中的 EvtCleanupCallback 事件回呼。
當 Windows 準備好釋放 DRIVER_OBJECT 結構並卸除驅動程式時,架構會叫用驅動程式的 EvtCleanupCallback 實作,向客戶端驅動程式報告該事件。 架構會在刪除架構驅動程式物件之前叫用該回呼。 用戶端驅動程式可以釋放其在 DriverEntry 中配置的所有全域資源。 例如,在範本程式代碼中,用戶端驅動程式會停止在 DriverEntry 中啟動的 WPP 追蹤。
下列程式代碼範例顯示客戶端驅動程式的 EvtCleanupCallback 事件回呼實作。
VOID MyUSBDriver_EvtDriverContextCleanup(
_In_ WDFDRIVER Driver
)
{
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE ();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
//
// Stop WPP Tracing
//
WPP_CLEANUP( WdfDriverWdmGetDriverObject(Driver) );
}
USB 驅動程式堆疊辨識裝置之後,總線驅動程式會為裝置建立實體裝置物件 (PDO),並將 PDO 與裝置節點產生關聯。 裝置節點位於堆疊形成中,其中 PDO 位於底部。 每個堆疊都必須有一個 PDO,而且可以有篩選裝置物件(篩選 DOS)和其上方的函式裝置物件 (FDO)。 如需詳細資訊,請參閱 裝置節點和裝置堆疊。
此圖顯示範本驅動程式的裝置堆疊,MyUSBDriver_.sys。
請注意名為「我的USB裝置」的裝置堆疊。 USB 驅動程式堆疊會建立裝置堆疊的 PDO。 在此範例中,PDO 與 Usbhub3.sys 相關聯,這是 USB 驅動程式堆疊隨附的其中一個驅動程式。 作為裝置的函式驅動程式,用戶端驅動程序必須先建立裝置的 FDO,然後將它附加至裝置堆疊的頂端。
針對以 KMDF 為基礎的用戶端驅動程式,架構會代表用戶端驅動程式執行這些工作。 為了代表裝置的 FDO,架構會 建立架構裝置物件。 不過,客戶端驅動程式可以指定架構用來設定新物件的特定初始化參數。 當架構叫用驅動程式的 EvtDriverDeviceAdd 實作時,就會有機會提供給客戶端驅動程式。 建立物件並連結至裝置堆疊頂端的 FDO 之後,架構會將 WDFDEVICE 句柄提供給架構裝置物件。 藉由使用此句柄,用戶端驅動程式可以執行各種裝置相關作業。
下列程式代碼範例顯示客戶端驅動程式的 EvtDriverDeviceAdd 事件回呼實作。
NTSTATUS
MyUSBDriver_EvtDeviceAdd(
_In_ WDFDRIVER Driver,
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
NTSTATUS status;
UNREFERENCED_PARAMETER(Driver);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = MyUSBDriver_CreateDevice(DeviceInit);
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
在運行時間期間,EvtDriverDeviceAdd 的實作會使用 PAGED_CODE 巨集來檢查是否在適當的環境中呼叫例程,以便進行可分頁的程序代碼。 在宣告所有變數之後,請務必呼叫 巨集;否則,編譯會失敗,因為產生的來源檔案是 .c 檔案,而不是.cpp檔案。
用戶端驅動程式的 EvtDriverDeviceAdd 實作會呼叫MyUSBDriver_CreateDevice協助程式函式來執行必要的工作。
下列程式代碼範例顯示MyUSBDriver_CreateDevice協助程式函式。 MyUSBDriver_CreateDevice定義於 Device.c。
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
)
{
WDF_PNPPOWER_EVENT_CALLBACKS pnpPowerCallbacks;
WDF_OBJECT_ATTRIBUTES deviceAttributes;
PDEVICE_CONTEXT deviceContext;
WDFDEVICE device;
NTSTATUS status;
PAGED_CODE();
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&pnpPowerCallbacks);
pnpPowerCallbacks.EvtDevicePrepareHardware = MyUSBDriver_EvtDevicePrepareHardware;
WdfDeviceInitSetPnpPowerEventCallbacks(DeviceInit, &pnpPowerCallbacks);
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (NT_SUCCESS(status)) {
//
// Get the device context and initialize it. WdfObjectGet_DEVICE_CONTEXT is an
// inline function generated by WDF_DECLARE_CONTEXT_TYPE macro in the
// device.h header file. This function will do the type checking and return
// the device context. If you pass a wrong object handle
// it will return NULL and assert if run under framework verifier mode.
//
deviceContext = WdfObjectGet_DEVICE_CONTEXT(device);
deviceContext->PrivateDeviceData = 0;
//
// Create a device interface so that applications can find and talk
// to us.
//
status = WdfDeviceCreateDeviceInterface(
device,
&GUID_DEVINTERFACE_MyUSBDriver_,
NULL // ReferenceString
);
if (NT_SUCCESS(status)) {
//
// Initialize the I/O Package and any Queues
//
status = MyUSBDriver_QueueInitialize(device);
}
}
return status;
}
EvtDriverDeviceAdd 有兩個參數:前一次呼叫 DriverEntry 中建立的架構驅動程序物件的句柄,以及WDFDEVICE_INIT結構的指標。 架構會 配置WDFDEVICE_INIT 結構,並傳遞指標,讓客戶端驅動程式可以使用要建立之架構裝置物件的初始化參數填入結構。
在 EvtDriverDeviceAdd 實作中,用戶端驅動程式必須執行下列工作:
呼叫 WdfDeviceCreate 方法,以擷取新裝置物件的 WDFDEVICE 句柄。
WdfDeviceCreate 方法會使架構建立 FDO 的架構裝置物件,並將它附加至裝置堆疊的頂端。 在 WdfDeviceCreate 呼叫中,用戶端驅動程式必須執行下列工作:
- 在架構指定的 WDFDEVICE_INIT 結構中,指定用戶端驅動程式隨插即用 (PnP) 電源回呼例程的指標。 例程會先在 WDF_PNPPOWER_EVENT_CALLBACKS 結構中設定,然後藉由呼叫 WdfDeviceInitSetPnpPowerEventCallbacks 方法與WDFDEVICE_INIT相關聯。
Windows 元件、PnP 和電源管理員,將裝置相關要求傳送給驅動程式,以回應 PnP 狀態的變更(例如啟動、停止和移除)和電源狀態(例如工作或暫停)。 針對以 KMDF 為基礎的驅動程式,架構會攔截這些要求。 用戶端驅動程式可以使用 WdfDeviceCreate 呼叫,向架構註冊稱為 PnP 電源事件回呼的回呼例程,以取得要求的相關通知。 當 Windows 元件傳送要求時,如果客戶端驅動程式已註冊,架構會處理這些要求,並呼叫對應的 PnP 電源事件回呼。
用戶端驅動程序必須實作的其中一個 PnP 電源事件回呼例程是 EvtDevicePrepareHardware。 當 PnP 管理員啟動裝置時,會叫用該事件回呼。 下一節將討論 EvtDevicePrepareHardware 的實作。
- 指定驅動程式裝置內容結構的指標。 指標必須在呼叫 WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE 巨集 所初始化的 WDF_OBJECT_ATTRIBUTES 結構中設定。
裝置內容(有時稱為裝置延伸模組)是用來儲存特定裝置對象相關信息的數據結構(由客戶端驅動程式定義)。 用戶端驅動程式會將指標傳遞給架構的裝置內容。 架構會根據結構的大小配置記憶體區塊,並將該記憶體位置的指標儲存在架構裝置物件中。 用戶端驅動程式可以使用指標,在裝置內容的成員中存取和儲存資訊。 如需裝置內容的詳細資訊,請參閱 Framework 對象內容空間。
WdfDeviceCreate 呼叫完成之後,用戶端驅動程式會接收新架構裝置物件的句柄,此物件會將指標儲存為裝置內容架構所配置的記憶體區塊。 用戶端驅動程式現在可以呼叫 WdfObjectGet_DEVICE_CONTEXT巨集 來取得裝置內容的指標。
呼叫 WdfDeviceCreateDeviceInterface 方法,以註冊客戶端驅動程式的裝置介面 GUID。 應用程式可以使用這個 GUID 與驅動程式通訊。 GUID 常數會在標頭 public.h 中宣告。
設定 I/O 傳輸至裝置的佇列。 範本程式代碼會定義MyUSBDriver_QueueInitialize,這是設定佇列的協助程式式例程,如佇列原始程式碼一節所述。
裝置的原始程式碼
device 物件代表用戶端驅動程式在記憶體中載入之裝置的實例。 裝置物件的完整原始碼位於 Device.h 和 Device.c 中。
Device.h
Device.h 頭檔包含 public.h,其中包含專案中所有檔案所使用的通用宣告。
Device.h 中的下一個區塊會宣告客戶端驅動程序的裝置內容。
typedef struct _DEVICE_CONTEXT
{
WDFUSBDEVICE UsbDevice;
ULONG PrivateDeviceData; // just a placeholder
} DEVICE_CONTEXT, *PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE(DEVICE_CONTEXT)
DEVICE_CONTEXT結構是由用戶端驅動程式所定義,並儲存架構裝置對象的相關信息。 它會在 Device.h 中宣告,並包含兩個成員:架構 USB 目標裝置物件的句柄(稍後討論)和佔位元。 此結構將會在稍後的練習中展開。
Device.h 也包含 WDF_DECLARE_CONTEXT_TYPE 巨集,其會產生內嵌函式, WdfObjectGet_DEVICE_CONTEXT。 用戶端驅動程式可以呼叫該函式,從架構裝置物件擷取記憶體區塊的指標。
下列程式代碼行會宣告MyUSBDriver_CreateDevice,這是可擷取 USB 目標裝置物件的 WDFUSBDEVICE 句柄的協助程式函式。
NTSTATUS
MyUSBDriver_CreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
);
USBCreate 會以WDFDEVICE_INIT結構的指標作為其參數。 這是架構叫用用戶端驅動程式 EvtDriverDeviceAdd 實作時所傳遞的相同指標。 基本上,MyUSBDriver_CreateDevice執行 EvtDriverDeviceAdd 的工作。 上一節將討論 EvtDriverDeviceAdd 實作的原始程式碼。
Device.h 中的下一行會宣告 EvtDevicePrepareHardware 事件回呼例程的函式角色類型宣告。 事件回呼是由客戶端驅動程序實作,並執行設定 USB 裝置等工作。
EVT_WDF_DEVICE_PREPARE_HARDWARE MyUSBDriver_EvtDevicePrepareHardware;
Device.c
Device.c 實作檔案包含下列使用 alloc_text
pragma 的程式代碼區塊,指定驅動程式的 EvtDevicePrepareHardware 實作位於可分頁記憶體中。
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_CreateDevice)
#pragma alloc_text (PAGE, MyUSBDriver_EvtDevicePrepareHardware)
#endif
在 EvtDevicePrepareHardware 的實作中,用戶端驅動程式會執行 USB 特定的初始化工作。 這些工作包括註冊客戶端驅動程式、初始化 USB 特定的 I/O 目標物件,以及選取 USB 組態。 下表顯示架構所提供的特製化 I/O 目標物件。 如需詳細資訊,請參閱 USB I/O目標。
USB I/O 目標物件 (句柄) | 透過呼叫來取得句柄... | 描述 |
---|---|---|
USB 目標裝置物件 (WDFUSBDEVICE ) | WdfUsbTargetDeviceCreateWithParameters | 代表USB裝置,並提供方法來擷取裝置描述元,並將控制要求傳送至裝置。 |
USB 目標介面物件 (WDFUSBINTERFACE ) | WdfUsbTargetDeviceGetInterface | 表示個別介面,並提供客戶端驅動程式可以呼叫的方法,以選取替代設定並擷取設定的相關信息。 |
USB 目標管道物件 (WDFUSBPIPE) | WdfUsbInterfaceGetConfiguredPipe | 表示介面目前替代設定中設定之端點的個別管道。 USB 驅動程式堆疊會選取所選組態中的每個介面,並設定介面內每個端點的通道。 在USB術語中,該通道稱為 管道。 |
此程式代碼範例示範 EvtDevicePrepareHardware 的實作。
NTSTATUS
MyUSBDriver_EvtDevicePrepareHardware(
_In_ WDFDEVICE Device,
_In_ WDFCMRESLIST ResourceList,
_In_ WDFCMRESLIST ResourceListTranslated
)
{
NTSTATUS status;
PDEVICE_CONTEXT pDeviceContext;
WDF_USB_DEVICE_CREATE_CONFIG createParams;
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS configParams;
UNREFERENCED_PARAMETER(ResourceList);
UNREFERENCED_PARAMETER(ResourceListTranslated);
PAGED_CODE();
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Entry");
status = STATUS_SUCCESS;
pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
if (pDeviceContext->UsbDevice == NULL) {
//
// Specifying a client contract version of 602 enables us to query for
// and use the new capabilities of the USB driver stack for Windows 8.
// It also implies that we conform to rules mentioned in the documentation
// documentation for WdfUsbTargetDeviceCreateWithParameters.
//
WDF_USB_DEVICE_CREATE_CONFIG_INIT(&createParams,
USBD_CLIENT_CONTRACT_VERSION_602
);
status = WdfUsbTargetDeviceCreateWithParameters(Device,
&createParams,
WDF_NO_OBJECT_ATTRIBUTES,
&pDeviceContext->UsbDevice
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceCreateWithParameters failed 0x%x", status);
return status;
}
//
// Select the first configuration of the device, using the first alternate
// setting of each interface
//
WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES(&configParams,
0,
NULL
);
status = WdfUsbTargetDeviceSelectConfig(pDeviceContext->UsbDevice,
WDF_NO_OBJECT_ATTRIBUTES,
&configParams
);
if (!NT_SUCCESS(status)) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
"WdfUsbTargetDeviceSelectConfig failed 0x%x", status);
return status;
}
}
TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DRIVER, "%!FUNC! Exit");
return status;
}
以下是範本程式代碼所實作的用戶端驅動程式工作:
指定客戶端驅動程式的合約版本,以準備向 Windows 載入的基礎 USB 驅動程式堆疊註冊本身。
視USB裝置所連接的主機控制器而定,Windows 可以載入USB 3.0或USB 2.0驅動程式堆疊。 USB 3.0 驅動程式堆疊是 Windows 8 中的新功能,並支援 USB 3.0 規格所定義的數項新功能,例如串流功能。 新的驅動程式堆疊也會實作數項改進,例如更好的USB要求區塊追蹤和處理,這些區塊可透過一組新的URB例程取得。 想要使用這些功能或呼叫新例程的用戶端驅動程序必須指定USBD_CLIENT_CONTRACT_VERSION_602合約版本。 USBD_CLIENT_CONTRACT_VERSION_602客戶端驅動程序必須遵守一組特定規則。 如需這些規則的詳細資訊,請參閱 最佳做法:使用URB。
若要指定合約版本,用戶端驅動程序必須呼叫WDF_USB_DEVICE_CREATE_CONFIG_INIT巨集,以合約版本初始化WDF_USB_DEVICE_CREATE_CONFIG結構。
呼叫 WdfUsbTargetDeviceCreateWithParameters 方法。 方法需要架構裝置物件的句柄,用戶端驅動程式在驅動程式的 EvtDriverDeviceAdd 實作中呼叫 WdfDeviceCreate 來取得該物件。 WdfUsbTargetDeviceCreateWithParameters 方法:
- 向基礎 USB 驅動程式堆疊註冊客戶端驅動程式。
- 擷取 WDFUSBDEVICE 句柄至架構所建立的 USB 目標裝置物件。 範本程式代碼會將句柄儲存在其裝置內容中的USB目標裝置物件。 透過使用該句柄,用戶端驅動程式可以取得裝置的 USB 特定資訊。
您的客戶端驅動程式不會呼叫 Windows 8 版本 WDK 可用的新 URB 例程集。
如果您的用戶端驅動程式呼叫 WdfUsbTargetDeviceCreateWithParameters,USB 驅動程式堆疊會假設所有 URB 都是藉由呼叫 WdfUsbTargetDeviceCreateUrb 或 WdfUsbTargetDeviceCreateIsochUrb 來配置。 由這些方法配置的 URB 具有 USB 驅動程式堆疊所使用的不透明 URB 內容區塊,以加快處理速度。 如果客戶端驅動程式使用那些方法未配置的 URB,USB 驅動程式會產生錯誤檢查。
如需 URB 配置的詳細資訊,請參閱 配置和建置 URB。
您的客戶端驅動程式不打算遵守最佳做法:使用URB中所述的一組規則。
這類驅動程式不需要指定用戶端合約版本,因此必須略過步驟 1。
選取USB組態。
在範本程式代碼中,用戶端驅動程式會 選取USB裝置中的預設組態 。 默認組態包括裝置的組態 0,以及該組態內每個介面的替代設定 0。
若要選取預設組態,用戶端驅動程式會呼叫 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES 函式來設定WDF_USB_DEVICE_SELECT_CONFIG_PARAMS結構。 函式會將 Type 成員初始化為 WdfUsbTargetDeviceSelectConfigTypeMultiInterface ,以指出如果有多個介面可用,則必須選取每個介面中的替代設定。 由於呼叫必須選取預設組態,客戶端驅動程式會在 SettingPairs 參數中指定 NULL,並在 NumberInterfaces 參數中指定 0。 完成時,WDF_USB_DEVICE_SELECT_CONFIG_PARAMS的 MultiInterface.NumberOfConfiguredInterfaces 成員會指出已選取 [替代設定 0] 的介面數目。 不會修改其他成員。
注意 如果客戶端驅動程式想要選取預設設定以外的替代設定,驅動程式必須建立WDF_USB_INTERFACE_SETTING_PAIR結構的數位。 陣列中的每個元素都會指定要選取的裝置定義介面編號和替代設定的索引。 該資訊會儲存在裝置的組態和介面描述元中,可藉由呼叫 WdfUsbTargetDeviceRetrieveConfigDescriptor 方法取得。 然後,客戶端驅動程式必須呼叫 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES,並將WDF_USB_INTERFACE_SETTING_PAIR陣列傳遞至架構。
佇列原始程式碼
架構 佇列物件 代表特定架構裝置物件的 I/O 佇列。 佇列物件的完整原始程式碼位於 Queue.h 和 Queue.c 中。
Queue.h
宣告架構佇列物件所引發之事件的事件回呼例程。
Queue.h 中的第一個區塊會宣告佇列內容。
typedef struct _QUEUE_CONTEXT {
ULONG PrivateDeviceData; // just a placeholder
} QUEUE_CONTEXT, *PQUEUE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(QUEUE_CONTEXT, QueueGetContext)
類似於裝置內容,佇列內容是由用戶端定義的數據結構,用來儲存特定佇列的相關信息。
下一行程式代碼會宣告MyUSBDriver_QueueInitialize函式,這個協助程式函式會建立和初始化架構佇列物件。
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
);
下一個程式代碼範例會宣告 EvtIoDeviceControl 事件回呼例程的函式角色類型宣告。 事件回呼是由客戶端驅動程序實作,並在架構處理裝置 I/O 控件要求時叫用。
EVT_WDF_IO_QUEUE_IO_DEVICE_CONTROL MyUSBDriver_EvtIoDeviceControl;
Queue.c
實作檔案 Queue.c 包含下列程式代碼區塊,該區塊會使用 alloc_text
pragma 來指定驅動程式的 MyUSBDriver_QueueInitialize 實作位於可分頁記憶體中。
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, MyUSBDriver_QueueInitialize)
#endif
WDF 提供架構佇列物件,以處理用戶端驅動程式的要求流程。 當客戶端驅動程式呼叫 WdfIoQueueCreate 方法時,架構會建立架構佇列物件。 在該呼叫中,用戶端驅動程式可以在架構建立佇列之前指定特定組態選項。 這些選項包括佇列是電源管理、允許零長度要求,還是驅動程序的預設佇列。 單一架構佇列物件可以處理數種類型的要求,例如讀取、寫入和裝置 I/O 控制件。 用戶端驅動程式可以為每個要求指定事件回呼。
用戶端驅動程式也必須指定分派類型。 佇列物件的分派類型會決定架構如何將要求傳遞至客戶端驅動程式。 傳遞機制可以是循序、平行,或由客戶端驅動程式所定義的自定義機制。 針對循序佇列,在用戶端驅動程式完成先前的要求之前,不會傳遞要求。 在平行分派模式中,架構會在要求從 I/O 管理員抵達時立即轉送要求。 這表示客戶端驅動程式可以在處理另一個要求時收到一個要求。 在自定義機制中,當驅動程式準備好處理它時,用戶端會手動從架構佇列物件提取下一個要求。
一般而言,客戶端驅動程序必須在驅動程式的 EvtDriverDeviceAdd 事件回呼中設定佇列。 範本程式代碼會提供協助程式例程,MyUSBDriver_QueueInitialize初始化架構佇列物件。
NTSTATUS
MyUSBDriver_QueueInitialize(
_In_ WDFDEVICE Device
)
{
WDFQUEUE queue;
NTSTATUS status;
WDF_IO_QUEUE_CONFIG queueConfig;
PAGED_CODE();
//
// Configure a default queue so that requests that are not
// configure-fowarded using WdfDeviceConfigureRequestDispatching to goto
// other queues get dispatched here.
//
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&queueConfig,
WdfIoQueueDispatchParallel
);
queueConfig.EvtIoDeviceControl = MyUSBDriver_EvtIoDeviceControl;
status = WdfIoQueueCreate(
Device,
&queueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&queue
);
if( !NT_SUCCESS(status) ) {
TraceEvents(TRACE_LEVEL_ERROR, TRACE_QUEUE, "WdfIoQueueCreate failed %!STATUS!", status);
return status;
}
return status;
}
若要設定佇列,用戶端驅動程式會執行下列工作:
- 在WDF_IO_QUEUE_CONFIG結構中指定佇列的組態選項。 範本程式代碼會使用 WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE 函式來初始化 結構。 函式會將佇列物件指定為預設佇列物件、受電源管理,並平行接收要求。
- 為佇列的 I/O 要求新增用戶端驅動程式的事件回呼。 在範本中,客戶端驅動程式會針對裝置 I/O 控制項要求指定其事件回呼的指標。
- 呼叫 WdfIoQueueCreate ,以擷取架構所建立之架構佇列物件的 WDFQUEUE 句柄。
以下是佇列機制的運作方式。 若要與 USB 裝置通訊,應用程式會先呼叫 SetDixxx 例程和 CreateHandle 來開啟裝置的句柄。 藉由使用此句柄,應用程式會使用特定的控制程式代碼呼叫 DeviceIoControl 函式。 根據控制項程式代碼的類型,應用程式可以在該呼叫中指定輸入和輸出緩衝區。 I/O 管理員最終會收到呼叫,然後建立要求 (IRP),並將其轉送至客戶端驅動程式。 架構會攔截要求、建立架構要求物件,並將它新增至架構佇列物件。 在此情況下,因為客戶端驅動程式註冊了裝置 I/O 控制項要求的事件回呼,因此架構會叫用回呼。 此外,因為佇列物件是使用 WdfIoQueueDispatchParallel 旗標建立的,因此一旦要求新增至佇列,就會叫用回呼。
VOID
MyUSBDriver_EvtIoDeviceControl(
_In_ WDFQUEUE Queue,
_In_ WDFREQUEST Request,
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode
)
{
TraceEvents(TRACE_LEVEL_INFORMATION,
TRACE_QUEUE,
"!FUNC! Queue 0x%p, Request 0x%p OutputBufferLength %d InputBufferLength %d IoControlCode %d",
Queue, Request, (int) OutputBufferLength, (int) InputBufferLength, IoControlCode);
WdfRequestComplete(Request, STATUS_SUCCESS);
return;
}
當架構叫用用戶端驅動程式的事件回呼時,它會將句柄傳遞給保存應用程式所傳送之要求的架構要求物件,以及其輸入和輸出緩衝區。 此外,它會將句柄傳送至包含要求的架構佇列物件。 在事件回呼中,用戶端驅動程序會視需要處理要求。 範本程式代碼只會完成要求。 用戶端驅動程式可以執行更多相關的工作。 例如,如果應用程式要求特定裝置資訊,在事件回呼中,客戶端驅動程式可以建立USB控件要求,並將它傳送至USB驅動程式堆疊,以擷取要求的裝置資訊。 USB 控制要求會在 USB 控制傳輸中討論。