瞭解 (KMDF) 的 USB 用戶端驅動程式程式代碼結構
在本主題中,您將瞭解 KMDF 型 USB 用戶端驅動程式的原始程式碼。 程式代碼範例是由 Microsoft Visual Studio 2019 隨附的 USB 使用者模式驅動程式範本所產生。
這些區段提供範本程式代碼的相關信息。
如需產生 KMDF 範本程式代碼的指示,請參閱 如何撰寫第一個 USB 用戶端驅動程式 (KMDF) 。
驅動程式原始碼
驅動程式物件代表 Windows 在記憶體中載入驅動程式之後,用戶端驅動程序的實例。 驅動程式物件的完整原始碼位於 Driver.h 和 Driver.c 中。
Driver.h
在討論範本程式代碼的詳細數據之前,讓我們先查看頭檔中的一些宣告, (Driver.h) 與 KMDF 驅動程式開發有關。
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。 驅動程式會在 WDF_DRIVER_CONFIG 結構中指定其實作 EvtDriverDeviceAdd 的指標,以及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 呼叫中,用戶端驅動程式必須執行下列工作:
- 指定客戶端驅動程式即插即用 (PnP 的指標,) 架構指定 之WDFDEVICE_INIT 結構中的電源回呼例程。 例程會先在 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.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 目標物件 (句柄) | 藉由呼叫來取得句柄... | Description |
---|---|---|
WDFUSBDEVICE (USB 目標裝置物件 ) | WdfUsbTargetDeviceCreateWithParameters | 表示USB裝置,並提供方法來擷取裝置描述項,並將控制要求傳送至裝置。 |
WDFUSBINTERFACE ) WDFUSBINTERFACE (USB 目標介面物件 | WdfUsbTargetDeviceGetInterface | 表示個別介面,並提供客戶端驅動程式可以呼叫的方法,以選取替代設定並擷取設定的相關信息。 |
WDFUSBPIPE (USB 目標管道物件) | 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要求區塊 (URI) ,這些動作可透過一組新的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 驅動程式堆疊註冊客戶端驅動程式。
- 擷取由架構所建立之 USB 目標裝置物件的 WDFUSBDEVICE 句柄。 範本程式代碼會將USB目標裝置物件的句柄儲存在其裝置內容中。 藉由使用該句柄,用戶端驅動程式可以取得裝置的USB特定資訊。
如果下列情況,您必須呼叫 WdfUsbTargetDeviceCreate ,而不是 WdfUsbTargetDeviceCreateWithParameters :
您的客戶端驅動程式不會呼叫 Windows 8 版 WDK 可用的新 URB 例程集。
如果您的用戶端驅動程式呼叫 WdfUsbTargetDeviceCreateWithParameters,USB 驅動程式堆棧會假設所有 URB 都是藉由呼叫 WdfUsbTargetDeviceCreateUrb 或 WdfUsbTargetDeviceCreateIsochUrb 來配置。 由這些方法配置的 URI 具有 USB 驅動程式堆疊所使用的不透明 URB 內容區塊,以加快處理速度。 如果客戶端驅動程式使用這些方法未配置的 URB,USB 驅動程式會產生錯誤檢查。
如需 URB 配置的詳細資訊,請參閱 配置和建置 URI。
您的客戶端驅動程式不打算遵守 最佳做法:使用 URB 中所述的規則集。
這類驅動程式不需要指定用戶端合約版本,因此必須略過步驟 1。
選取USB組態。
在範本程式代碼中,用戶端驅動程式會在 USB 裝置中選取 預設組態 。 默認設定包括裝置的組態 0,以及該組態內每個介面的替代設定 0。
若要選取預設組態,用戶端驅動程式會呼叫 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS_INIT_MULTIPLE_INTERFACES 函式來設定 WDF_USB_DEVICE_SELECT_CONFIG_PARAMS 結構。 函式會將 Type 成員初始化為 WdfUsbTargetDeviceSelectConfigTypeMultiInterface ,以指出如果有多個介面可用,則必須選取每個介面中的替代設定。 由於呼叫必須選取預設組態,所以用戶端驅動程式會在 NumberInterfaces 參數的 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 控制傳輸中討論。
相關主題
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應