使用虛擬 HID Framework (VHF) 撰寫 HID 來源驅動程式

本主題將說明如何:

  • 撰寫 Kernel-Mode Driver Framework (KMDF) HID 來源驅動程式,以將 HID 讀取報告提交至 Windows。
  • 將 VHF 驅動程式載入為虛擬 HID 裝置堆疊中 HID 來源驅動程式的較低篩選器。

瞭解如何撰寫將 HID 數據報告至作業系統的 HID 來源驅動程式。

HID 輸入裝置,例如鍵盤、滑鼠、手寫筆、觸控或按鈕,會將各種報表傳送至操作系統,以便了解裝置的目的並採取必要的動作。 報告的格式為 HID 集合HID 使用量。 裝置會透過各種傳輸傳送這些報告,其中一些由 Windows 支援,例如 透過 I2C 的 HID透過 USB 的 HID。 在某些情況下,Windows 可能不支援傳輸,或報表可能不會直接對應至實際硬體。 它可以是 HID 格式的數據串流,由另一個軟體元件針對虛擬硬體傳送,例如非 GPIO 按鈕或感測器。 例如,請考慮使用手機的加速計數據做為遊戲控制器,以無線方式傳送至計算機。 在另一個範例中,計算機可以使用UIBC通訊協定從Miracast裝置接收遠端輸入。

在舊版 Windows 中,若要支援新的傳輸 (實際的硬體或軟體) ,您必須撰寫 HID 傳輸迷你驅動程式 ,並將其系結至 Microsoft 提供的現成類別驅動程式,Hidclass.sys。 類別/迷你驅動程式配對提供 HID 集合,例如 最上層集合 到高階驅動程式和使用者模式應用程式。 在該模型中,挑戰是撰寫迷你驅動程式,這可以是複雜的工作。

從 Windows 10 開始,新的虛擬 HID Framework (VHF) 不需要撰寫傳輸迷你驅動程式。 您可以改為使用 KMDF 或 WDM 程式設計介面來撰寫 HID 來源驅動程式。 架構是由 Microsoft 提供的靜態連結庫所組成,可公開驅動程式所使用的程式設計專案。 它也包含 Microsoft 提供的現成驅動程式,可列舉一或多個子裝置,並繼續建置和管理虛擬 HID 樹狀結構。

注意

在此版本中,VHF 僅支援核心模式中的 HID 來源驅動程式。

本主題描述架構的架構、虛擬 HID 裝置樹狀結構,以及組態案例。

虛擬 HID 裝置樹狀結構

在此影像中,裝置樹狀結構會顯示驅動程式及其相關聯的裝置物件。

虛擬 HID 裝置樹狀結構的圖表。

HID 來源驅動程式 (驅動程式)

HID 來源驅動程式會連結至 Vhfkm.lib,並在其建置專案中包含 Vhf.h。 驅動程式可以使用 Windows 驅動程式模型 (WDM) ,或 Kernel-Mode 驅動程式架構 (KMDF) 屬於 Windows 驅動程式架構 (WDF) 的一部分來撰寫驅動程式。 驅動程式可以載入為篩選驅動程式或裝置堆疊中的函式驅動程式。

VHF 靜態庫 (vhfkm.lib)

靜態連結庫包含在適用於 Windows 10 的 Windows 驅動程式套件 (WDK) 中。 連結庫會公開程序設計介面,例如 HID 來源驅動程式所使用的例程和回呼函式。 當您的驅動程式呼叫函式時,靜態連結庫會將要求轉送到處理要求的 VHF 驅動程式。

VHF 驅動程式 (Vhf.sys)

Microsoft 提供的現成驅動程式。 此驅動程式必須載入為 HID 來源裝置堆疊中驅動程式下方的較低篩選驅動程式。 VHF 驅動程式會動態列舉子裝置,並針對您 HID 來源驅動程式指定的一或多個 HID 裝置, (PDO) 建立實體裝置物件。 它也會實作列舉子裝置的 HID 傳輸迷你驅動程式功能。

HID 類別驅動程式組 (Hidclass.sys,Mshidkmdf.sys)

Hidclass/Mshidkmdf 配對會列舉 最上層集合 (TLC) 類似於它如何列舉真實 HID 裝置的這些集合。 HID 用戶端可以繼續要求和使用 TLC,就像真正的 HID 裝置一樣。 此驅動程式組會安裝為裝置堆疊中的函式驅動程式。

注意

在某些情況下,HID 用戶端可能需要識別 HID 數據的來源。 例如,系統具有內建感測器,並從相同類型的遠端感測器接收數據。 系統可能會想要選擇一個感測器,以更可靠。 為了區分聯機至系統的兩個感測器,HID 用戶端會查詢 TLC 的容器識別碼。 在此情況下,HID 來源驅動程式可以提供容器標識碼,該標識碼會由 VHF 回報為虛擬 HID 裝置的容器識別碼。

HID 用戶端 (應用程式)

查詢和使用 HID 裝置堆疊所報告的 TLC。

標頭和連結庫需求

此程式描述如何撰寫簡單的 HID 來源驅動程式,以將頭戴裝置按鈕報告給操作系統。 在此情況下,實作此程式代碼的驅動程式可以是已修改為使用 VHF 作為 HID 來源報告頭戴式裝置按鈕的現有 KMDF 音訊驅動程式。

  1. 包含 Vhf.h,包含在 WDK 中的 Windows 10。

  2. 連結至 vhfkm.lib,包含在 WDK 中。

  3. 建立您的裝置想要向操作系統回報的 HID 報表描述元。 在此範例中,HID 報表描述元會描述頭戴式裝置按鈕。 報表會指定 HID 輸入報表,大小為 8 位, (1 位元組) 。 前三個位適用於頭戴式裝置中間、音量向上和音量向下按鈕。 其餘位未使用。

    UCHAR HeadSetReportDescriptor[] = {
        0x05, 0x01,         // USAGE_PAGE (Generic Desktop Controls)
        0x09, 0x0D,         // USAGE (Portable Device Buttons)
        0xA1, 0x01,         // COLLECTION (Application)
        0x85, 0x01,         //   REPORT_ID (1)
        0x05, 0x09,         //   USAGE_PAGE (Button Page)
        0x09, 0x01,         //   USAGE (Button 1 - HeadSet : middle button)
        0x09, 0x02,         //   USAGE (Button 2 - HeadSet : volume up button)
        0x09, 0x03,         //   USAGE (Button 3 - HeadSet : volume down button)
        0x15, 0x00,         //   LOGICAL_MINIMUM (0)
        0x25, 0x01,         //   LOGICAL_MAXIMUM (1)
        0x75, 0x01,         //   REPORT_SIZE (1)
        0x95, 0x03,         //   REPORT_COUNT (3)
        0x81, 0x02,         //   INPUT (Data,Var,Abs)
        0x95, 0x05,         //   REPORT_COUNT (5)
        0x81, 0x03,         //   INPUT (Cnst,Var,Abs)
        0xC0,               // END_COLLECTION
    };
    

建立虛擬 HID 裝置

呼叫 VHF_CONFIG_INIT 宏,然後呼叫 VhfCreate 方法,以初始化VHF_CONFIG結構。 驅動程式必須在 WdfDeviceCreate 呼叫之後於PASSIVE_LEVEL呼叫 VhfCreate,通常是在驅動程式的 EvtDriverDeviceAdd 回呼函式中。

VhfCreate 呼叫中,驅動程式可以指定特定組態選項,例如必須以異步方式處理的作業,或設定裝置資訊 (廠商/產品標識碼) 。

例如,應用程式要求TLC。 當 HID 類別驅動程式配對收到該要求時,配對會決定要求的類型,並建立適當的 HID Minidriver IOCTL 要求,並將其轉送到 VHF。 取得IOCTL要求時,VHF可以處理要求、依賴HID來源驅動程序來處理要求,或使用STATUS_NOT_SUPPORTED完成要求。

VHF 會處理下列 IOCTLs:

如果要求為 GetFeatureSetFeatureWriteReportGetInputReport,且 HID 來源驅動程式已註冊對應的回呼函式,VHF 會叫用回呼函式。 在該函式中,HID 來源驅動程式可以取得或設定 HID 虛擬裝置的 HID 數據。 如果驅動程式未註冊回呼,VHF 會以狀態STATUS_NOT_SUPPORTED完成要求。

VHF 會針對這些 IOCTLs 叫用 HID 來源驅動程序實作的事件回呼函式:

針對任何其他 HID Minidriver IOCTL,VHF 會使用 STATUS_NOT_SUPPORTED 來完成要求。

虛擬 HID 裝置會藉由呼叫 VhfDelete 來刪除。 如果驅動程式為虛擬 HID 裝置配置資源,則需要 EvtVhfCleanup 回呼。 驅動程式必須實作 EvtVhfCleanup 函式,並在 VHF_CONFIGEvtVhfCleanup 成員中指定該函式的指標。 EvtVhfCleanup 會在 VhfDelete 呼叫完成之前叫用。 如需詳細資訊,請參閱 刪除虛擬 HID 裝置

注意

異步操作完成後,驅動程式必須呼叫 VhfAsyncOperationComplete 來設定作業的結果。 您可以從事件回呼或稍後從回呼傳回之後呼叫 方法。

NTSTATUS
VhfSourceCreateDevice(
_Inout_ PWDFDEVICE_INIT DeviceInit
)

{
    WDF_OBJECT_ATTRIBUTES   deviceAttributes;
    PDEVICE_CONTEXT deviceContext;
    VHF_CONFIG vhfConfig;
    WDFDEVICE device;
    NTSTATUS status;

    PAGED_CODE();

    WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes, DEVICE_CONTEXT);
    deviceAttributes.EvtCleanupCallback = VhfSourceDeviceCleanup;

    status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);

    if (NT_SUCCESS(status))
    {
        deviceContext = DeviceGetContext(device);

        VHF_CONFIG_INIT(&vhfConfig,
            WdfDeviceWdmGetDeviceObject(device),
            sizeof(VhfHeadSetReportDescriptor),
            VhfHeadSetReportDescriptor);

        status = VhfCreate(&vhfConfig, &deviceContext->VhfHandle);

        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "VhfCreate failed %!STATUS!", status);
            goto Error;
        }

        status = VhfStart(deviceContext->VhfHandle);
        if (!NT_SUCCESS(status)) {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE, "VhfStart failed %!STATUS!", status);
            goto Error;
        }

    }

Error:
    return status;
}

提交 HID 輸入報告

呼叫 VhfReadReportSubmit 來提交 HID 輸入報告。

一般而言,HID 裝置會透過中斷傳送輸入報告來傳送狀態變更的相關信息。 例如,當按鈕的狀態變更時,頭戴裝置可能會傳送報告。 在這種情況下,會叫用驅動程式的中斷服務例程 (ISR) 。 在該例程中,驅動程式可能會排程延遲的程式呼叫, (DPC) 處理輸入報告,並將它提交至 VHF,以將資訊傳送至操作系統。 根據預設,VHF 會緩衝報表,而 HID 來源驅動程式可以開始提交 HID 輸入報告,因為它們會傳入。 如此一來,就不需要 HID 來源驅動程式實作複雜的同步處理。

HID 來源驅動程式可以藉由實作擱置報表的緩衝原則來提交輸入報告。 為了避免重複緩衝,HID 來源驅動程式可以實作 EvtVhfReadyForNextReadReport 回呼函式,並追蹤 VHF 是否叫用此回呼。 如果先前已叫用,HID 來源驅動程式可以呼叫 VhfReadReportSubmit 來提交報表。 它必須先等候 EvtVhfReadyForNextReadReport 叫用,才能再次呼叫 VhfReadReportSubmit

VOID
MY_SubmitReadReport(
    PMY_CONTEXT  Context,
    BUTTON_TYPE  ButtonType,
    BUTTON_STATE ButtonState
    )
{
    PDEVICE_CONTEXT deviceContext = (PDEVICE_CONTEXT)(Context);

    if (ButtonState == ButtonStateUp) {
        deviceContext->VhfHidReport.ReportBuffer[0] &= ~(0x01 << ButtonType);
    } else {
        deviceContext->VhfHidReport.ReportBuffer[0] |=  (0x01 << ButtonType);
    }

    status = VhfReadReportSubmit(deviceContext->VhfHandle, &deviceContext->VhfHidReport);

    if (!NT_SUCCESS(status)) {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,"VhfReadReportSubmit failed %!STATUS!", status);
    }
}

刪除虛擬 HID 裝置

呼叫 VhfDelete 來刪除虛擬 HID 裝置。

您可以藉由指定 Wait 參數,以同步或異步方式呼叫 VhfDelete。 針對同步呼叫,必須在PASSIVE_LEVEL呼叫 方法,例如從裝置物件的 EvtCleanupCallback 呼叫。 VhfDelete 會在刪除虛擬 HID 裝置之後傳回。 如果驅動程式以異步方式呼叫 VhfDelete ,它會立即傳回,而 VHF 會在刪除作業完成後叫用 EvtVhfCleanup 。 方法最多可以DISPATCH_LEVEL呼叫。 在此情況下,驅動程式必須在先前呼叫 VhfCreate 時註冊並實作 EvtVhfCleanup 回呼函式。 以下是 HID 來源驅動程式想要移除虛擬 HID 裝置時的事件順序:

  1. HID 來源驅動程式會停止對 VHF 起始呼叫。
  2. HID 來源會呼叫 VhfDelete並將 Wait 設定為 FALSE。
  3. VHF 會停止叫用 HID 來源驅動程式所實作的回呼函式。
  4. VHF 會開始將裝置回報為 PnP 管理員遺失。 此時,VhfDelete 呼叫可能會傳回。
  5. 當裝置回報為遺失的裝置時,如果 HID 來源驅動程式已註冊其實作, VHF 會叫用 EvtVhfCleanup
  6. EvtVhfCleanup 傳回之後,VHF 會執行其清除。
VOID
VhfSourceDeviceCleanup(
_In_ WDFOBJECT DeviceObject
)
{
    PDEVICE_CONTEXT deviceContext;

    PAGED_CODE();

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

    deviceContext = DeviceGetContext(DeviceObject);

    if (deviceContext->VhfHandle != WDF_NO_HANDLE)
    {
        VhfDelete(deviceContext->VhfHandle, TRUE);
    }

}

安裝 HID 來源驅動程式

在安裝 HID 來源驅動程式的 INF 檔案中,使用 AddReg 指示詞,確定您已將 Vhf.sys 宣告為 HID 來源驅動程式的較低篩選器驅動程式。

[HIDVHF_Inst.NT.HW]
AddReg = HIDVHF_Inst.NT.AddReg

[HIDVHF_Inst.NT.AddReg]
HKR,,"LowerFilters",0x00010000,"vhf"