共用方式為


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

本篇文章說明如何︰

  • 撰寫 Kernel-Mode 驅動程序架構 (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 架構 (VHF) 不需要撰寫傳輸迷你驅動程式。 相反地,您可以使用 KMDF 或 WDM 程式設計介面來撰寫 HID 來源驅動程式。 架構由 Microsoft 提供的靜態函式庫組成,該函式庫揭露驅動程式所使用的程式設計元素。 它也包含Microsoft提供的內建驅動程式,可列舉一或多個子裝置,並繼續建置和管理虛擬 HID 樹狀結構。

備註

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

本文說明架構的架構、虛擬 HID 裝置樹狀結構,以及設定案例。

虛擬 HID 裝置樹狀結構

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

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

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

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

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 組會列舉 Top-Level Collections (TLC), 類似於其列舉真實 HID 裝置的這些集合的方式。 HID 用戶端可以繼續要求和使用 TLC,就像真正的 HID 裝置一樣。 此驅動程式組會安裝為裝置堆疊中的函式驅動程式。

備註

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

HID 用戶端 (應用程式)

查詢並取用 HID 裝置堆疊所報告的 TLC。

標頭與函式庫需求

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

  1. 包含自 WDK 的 Vhf.h。

  2. 連結至 WDK 中所包含的 vhfkm.lib。

  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 巨集來初始化VHF_CONFIG結構,然後呼叫 VhfCreate 方法。 驅動程式必須在 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呼叫 方法,例如來自裝置物件的 EvtCleanupCallbackVhfDelete 會在刪除虛擬 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"