仮想 HID フレームワーク (VHF) を使って HID ソース ドライバーを作成する

このトピックでは、次の方法を説明します。

  • WINDOWSに HID 読み取りレポートを送信するKernel-Mode Driver Framework (KMDF) HID ソース ドライバーを記述します。
  • VHF ドライバーを下位フィルターとして、仮想 HID デバイス スタックの HID ソース ドライバーに読み込みます。

HID データをオペレーティング システムに報告する HID ソース ドライバーの記述について説明します。

キーボード、マウス、ペン、タッチ、ボタンなどの HID 入力デバイスは、デバイスの目的を理解し、必要なアクションを実行できるように、オペレーティング システムにさまざまなレポートを送信します。 レポートは、 HID コレクションHID 使用法の形式です。 デバイスは、I2C 経由の HID や USB 経由の HID など、Windowsでサポートされている一部のトランスポートを介してこれらのレポートを送信します。 場合によっては、トランスポートがWindowsでサポートされていない場合や、レポートが実際のハードウェアに直接マップされない場合があります。 これは、GPIO 以外のボタンやセンサーなど、仮想ハードウェア用の別のソフトウェア コンポーネントによって送信される HID 形式のデータストリームです。 たとえば、ゲーム コントローラーとして動作し、PC にワイヤレスで送信される電話からの加速度計データを考えてみましょう。 別の例では、コンピューターは UIBC プロトコルを使用して、Miracast デバイスからリモート入力を受信できます。

以前のバージョンのWindowsでは、新しいトランスポート (実際のハードウェアまたはソフトウェア) をサポートするには、HID トランスポート ミニドライバーを作成し、Microsoft が提供するインボックス クラス ドライバーにバインドする必要Hidclass.sys。 クラス/ミニ ドライバー ペアは、 最上位レベル のコレクションなどの HID コレクションを上位レベルのドライバーやユーザー モード アプリケーションに提供しました。 そのモデルでは、複雑なタスクになる可能性があるミニドライバーを記述することが課題でした。

Windows 10以降、新しい Virtual HID Framework (VHF) により、トランスポート ミニドライバーを記述する必要がなくなります。 代わりに、KMDF または WDM プログラミング インターフェイスを使用して HID ソース ドライバーを作成できます。 フレームワークは、ドライバーによって使用されるプログラミング要素を公開する Microsoft 提供の静的ライブラリで構成されます。 また、1 つ以上の子デバイスを列挙し、仮想 HID ツリーの構築と管理に進む、Microsoft 提供のインボックス ドライバーも含まれています。

注意

このリリースでは、VHF はカーネル モードでのみ HID ソース ドライバーをサポートしています。

このトピックでは、フレームワークのアーキテクチャ、仮想 HID デバイス ツリー、および構成シナリオについて説明します。

仮想 HID デバイス ツリー

この図では、デバイス ツリーにドライバーとそれに関連付けられているデバイス オブジェクトが表示されます。

Diagram of a virtual HID device tree.

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 ソース ドライバーで指定されている 1 つ以上の HID デバイスの物理デバイス オブジェクト (PDO) を作成します。 また、列挙子デバイスの HID トランスポート ミニ ドライバー機能も実装します。

HID クラス ドライバー ペア (Hidclass.sys、Mshidkmdf.sys)

Hidclass/Mshidkmdf ペアは、実際の HID デバイスのコレクションを列挙する方法と同様に 、最上位のコレクション (TLC) を列挙します。 HID クライアントは、実際の HID デバイスと同様に、引き続き TLC を要求して使用できます。 このドライバー ペアは、デバイス スタックのファンクション ドライバーとしてインストールされます。

Note

一部のシナリオでは、HID クライアントが HID データのソースを識別する必要がある場合があります。 たとえば、システムにはセンサーが組み込まれており、同じ種類のリモート センサーからデータを受信します。 システムでは、信頼性を高めるために 1 つのセンサーを選択する必要がある場合があります。 システムに接続されている 2 つのセンサーを区別するために、HID クライアントは TLC のコンテナー ID を照会します。 この場合、HID ソース ドライバーは、VHF によって仮想 HID デバイスのコンテナー ID として報告されるコンテナー ID を提供できます。

HID クライアント (アプリケーション)

HID デバイス スタックによって報告される TLC を照会して使用します。

ヘッダーとライブラリの要件

この手順では、ヘッドセット ボタンをオペレーティング システムに報告する簡単な HID ソース ドライバーを記述する方法について説明します。 この場合、このコードを実装するドライバーは、VHF を使用して HID ソース レポート ヘッドセット ボタンとして機能するように変更された既存の KMDF オーディオ ドライバーにすることができます。

  1. Windows 10の WDK に含まれる Vhf.h を含めます。

  2. WDK に含まれている vhfkm.lib へのリンク。

  3. デバイスがオペレーティング システムに報告する HID レポート記述子を作成します。 この例では、HID レポート記述子でヘッドセット ボタンについて説明します。 レポートでは、HID 入力レポート、サイズ 8 ビット (1 バイト) を指定します。 最初の 3 ビットは、ヘッドセットの中央、音量を上げるボタン、音量を下げるボタン用です。 残りのビットは使用されません。

    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 メソッドを呼び出します。 ドライバーは、通常、ドライバーの EvtDriverDeviceAdd コールバック関数で、WdfDeviceCreate 呼び出しの後、PASSIVE_LEVELで VhfCreate を呼び出す必要があります。

VhfCreate 呼び出しでは、ドライバーは、非同期で処理する必要がある操作やデバイス情報 (ベンダー/製品 ID) の設定など、特定の構成オプションを指定できます。

たとえば、アプリケーションが TLC を要求するとします。 HID クラス ドライバーペアがその要求を受信すると、ペアは要求の種類を決定し、適切な HID ミニドライバー IOCTL 要求を作成し、VHF に転送します。 IOCTL 要求を取得すると、VHF は要求を処理したり、HID ソース ドライバーを使用して要求を処理したり、STATUS_NOT_SUPPORTEDで要求を完了したりできます。

VHF は、次の IOCTL を処理します。

要求が GetFeatureSetFeatureWriteReport、または GetInputReport で、HID ソース ドライバーが対応するコールバック関数を登録している場合、VHF はコールバック関数を呼び出します。 その関数内で、HID ソース ドライバーは、HID 仮想デバイスの HID データを取得または設定できます。 ドライバーがコールバックを登録しない場合、VHF は状態STATUS_NOT_SUPPORTEDで要求を完了します。

VHF は、次の IOCTL に対して HID ソース ドライバー実装イベント コールバック関数を呼び出します。

  • IOCTL_HID_READ_REPORT

    HID 入力レポートを取得するためのバッファーの送信中にドライバーがバッファー ポリシーを処理する場合は、 EvtVhfReadyForNextReadReport を実装し、 EvtVhfAsyncOperationGetInputReport メンバーにポインターを指定する必要があります。 詳細については、「 HID 入力レポートを送信する」を参照してください。

  • IOCTL_HID_GET_FEATURE または IOCTL_HID_SET_FEATURE

    ドライバーが HID 機能レポートを非同期的に取得または設定する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGの EvtVhfAsyncOperationGetFeature または EvtVhfAsyncOperationSetFeature メンバーの get または set 実装関数へのポインターを指定する必要があります。

  • IOCTL_HID_GET_INPUT_REPORT

    ドライバーが HID 入力レポートを非同期的に取得する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGEvtVhfAsyncOperationGetInputReport メンバー内の関数へのポインターを指定する必要があります。

  • IOCTL_HID_WRITE_REPORT

    ドライバーが HID 入力レポートの書き込みを非同期的に取得する場合、ドライバーは EvtVhfAsyncOperation 関数を実装し、VHF_CONFIGEvtVhfAsyncOperationWriteReport メンバー内の関数へのポインターを指定する必要があります。

その他の HID ミニドライバー 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) が呼び出されます。 このルーチンでは、ドライバーは、入力レポートを処理し、VHF に送信する遅延プロシージャ コール (DPC) をスケジュールする可能性があります。この呼び出しは、オペレーティング システムに情報を送信します。 既定では、VHF はレポートをバッファーし、HID ソース ドライバーは、HID 入力レポートの送信を開始できます。 これにより、HID ソース ドライバーが複雑な同期を実装する必要がなくなります。

HID ソース ドライバーは、保留中のレポートのバッファリング ポリシーを実装することで、入力レポートを送信できます。 重複するバッファリングを回避するために、HID ソース ドライバーは EvtVhfReadyForNextReadReport コールバック関数を実装し、VHF がこのコールバックを呼び出したかどうかを追跡できます。 以前に呼び出された場合、HID ソース ドライバーは VhfReadReportSubmit を呼び出してレポートを送信できます。 VhfReadReportSubmit を再度呼び出す前に、EvtVhfReadyForNextReadReport が呼び出されるのを待つ必要があります。

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 デバイスを削除します。

VhfDelete は、Wait パラメーターを指定することで同期または非同期で呼び出すことができます。 同期呼び出しの場合、デバイス オブジェクトの EvtCleanupCallback など、PASSIVE_LEVELでメソッドを呼び出す必要があります。 VhfDelete は、仮想 HID デバイスを削除した後に返されます。 ドライバーが VhfDelete を非同期的に呼び出すと、すぐに返され、削除操作が完了した後 、VHF は EvtVhfCleanup を呼び出します。 このメソッドは、最大DISPATCH_LEVELで呼び出すことができます。 この場合、ドライバーは、以前に VhfCreate を呼び出したときに EvtVhfCleanup コールバック関数を登録して実装している必要があります。 HID ソース ドライバーが仮想 HID デバイスを削除する場合のイベントのシーケンスを次に示します。

  1. HID ソース ドライバーは、VHF への呼び出しの開始を停止します。
  2. HID ソースは、待機が FALSE に設定された VhfDelete を呼び出します。
  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 ディレクティブを使用して、HID ソース ドライバーに下位フィルター ドライバーとしてVhf.sys宣言していることを確認します。

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

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