Schreiben eines HID-Quelltreibers mithilfe von Virtual HID Framework (VHF)

In diesem Thema wird Folgendes erläutert:

  • Schreiben Sie einen Kernel-Mode Driver Framework (KMDF)HID-Quelltreiber, der HID-Leseberichte an Windows übermittelt.
  • Laden Sie den VHF-Treiber als unteren Filter in den HID-Quelltreiber im virtuellen HID-Gerätestapel.

Erfahren Sie mehr über das Schreiben eines HID-Quelltreibers, der HID-Daten an das Betriebssystem meldet.

Ein HID-Eingabegerät, z. B. eine Tastatur, Maus, Stift, Toucheingabe oder Schaltfläche, sendet verschiedene Berichte an das Betriebssystem, damit es den Zweck des Geräts verstehen und die erforderlichen Maßnahmen ergreifen kann. Die Berichte werden in Form von HID-Sammlungen und HID-Nutzungen erstellt. Das Gerät sendet diese Berichte über verschiedene Transporte, von denen einige von Windows unterstützt werden, z. B . HID über I2C und HID über USB. In einigen Fällen wird der Transport möglicherweise nicht von Windows unterstützt, oder die Berichte werden nicht direkt der tatsächlichen Hardware zugeordnet. Es kann sich um einen Datenstrom im HID-Format handelt, der von einer anderen Softwarekomponente für virtuelle Hardware gesendet wird, z. B. für Nicht-GPIO-Schaltflächen oder Sensoren. Betrachten Sie beispielsweise Beschleunigungsmesserdaten eines Smartphones, das sich als Gamecontroller verhält und drahtlos an einen PC gesendet wird. In einem anderen Beispiel kann ein Computer mithilfe des UIBC-Protokolls Remoteeingaben von einem Miracast-Gerät empfangen.

In früheren Versionen von Windows mussten Sie zur Unterstützung neuer Transporte (echte Hardware oder Software) einen HID-Transport-Minidriver schreiben und ihn an den von Microsoft bereitgestellten In-Box-Klassentreiber binden, Hidclass.sys. Das Klassen-/Mini-Treiberpaar stellt die HID-Sammlungen bereit, z. B. Sammlungen der obersten Ebene für Treiber der oberen Ebene und Anwendungen im Benutzermodus. In diesem Modell war die Herausforderung das Schreiben des Minidrivers, was eine komplexe Aufgabe sein kann.

Ab Windows 10 entfällt mit dem neuen Virtual HID Framework (VHF) das Schreiben eines Transportminidrivers. Stattdessen können Sie mithilfe von KMDF- oder WDM-Programmierschnittstellen einen HID-Quelltreiber schreiben. Das Framework besteht aus einer von Microsoft bereitgestellten statischen Bibliothek, die Von Ihrem Treiber verwendete Programmierelemente verfügbar macht. Es enthält auch einen von Microsoft bereitgestellten In-Box-Treiber, der mindestens ein untergeordnetes Gerät aufzählt und mit dem Erstellen und Verwalten einer virtuellen HID-Struktur fortfährt.

Hinweis

In diesem Release unterstützt VHF einen HID-Quelltreiber nur im Kernelmodus.

In diesem Thema werden die Architektur des Frameworks, die virtuelle HID-Gerätestruktur und die Konfigurationsszenarien beschrieben.

Virtuelle HID-Gerätestruktur

In dieser Abbildung zeigt die Gerätestruktur die Treiber und die zugehörigen Geräteobjekte.

Diagramm einer virtuellen HID-Gerätestruktur.

HID-Quelltreiber (Ihr Treiber)

Der HID-Quelltreiber verknüpft mit Vhfkm.lib und schließt Vhf.h in sein Buildprojekt ein. Der Treiber kann entweder mithilfe von Windows Driver Model (WDM) oder Kernel-Mode Driver Framework (KMDF) geschrieben werden, das Teil des Windows Driver Frameworks (WDF) ist. Der Treiber kann als Filtertreiber oder Als Funktionstreiber in den Gerätestapel geladen werden.

Statische VHF-Bibliothek (vhfkm.lib)

Die statische Bibliothek ist im Windows Driver Kit (WDK) für Windows 10 enthalten. Die Bibliothek macht Programmierschnittstellen wie Routinen und Rückruffunktionen verfügbar, die von Ihrem HID-Quelltreiber verwendet werden. Wenn Ihr Treiber eine Funktion aufruft, leitet die statische Bibliothek die Anforderung an den VHF-Treiber weiter, der die Anforderung verarbeitet.

VHF-Treiber (Vhf.sys)

Ein im Lieferumfang von Microsoft bereitgestellter Treiber. Dieser Treiber muss als niedrigerer Filtertreiber unterhalb Ihres Treibers im HID-Quellgerätestapel geladen werden. Der VHF-Treiber listet dynamisch untergeordnete Geräte auf und erstellt physische Geräteobjekte (PDO) für ein oder mehrere HID-Geräte, die von Ihrem HID-Quelltreiber angegeben werden. Außerdem wird die HID-Transport-Minitreiberfunktion der aufgezählten untergeordneten Geräte implementiert.

HID-Klassentreiberpaar (Hidclass.sys, Mshidkmdf.sys)

Das Paar Hidclass/Mshidkmdf listet Sammlungen der obersten Ebene (Top-Level Collections, TLC) ähnlich auf, wie es diese Sammlungen für ein echtes HID-Gerät aufzählt. Ein HID-Client kann die TLCs weiterhin wie ein echtes HID-Gerät anfordern und nutzen. Dieses Treiberpaar wird als Funktionstreiber im Gerätestapel installiert.

Hinweis

In einigen Szenarien muss ein HID-Client möglicherweise die Quelle der HID-Daten identifizieren. Beispielsweise verfügt ein System über einen integrierten Sensor und empfängt Daten von einem Remotesensor desselben Typs. Das System möchte möglicherweise einen Sensor auswählen, um zuverlässiger zu sein. Um zwischen den beiden Sensoren zu unterscheiden, die mit dem System verbunden sind, fragt der HID-Client die Container-ID des TLC ab. In diesem Fall kann ein HID-Quelltreiber die Container-ID bereitstellen, die von VHF als Container-ID des virtuellen HID-Geräts gemeldet wird.

HID-Client (Anwendung)

Fragt die TLCs ab, die vom HID-Gerätestapel gemeldet werden, und nutzt sie.

Header- und Bibliotheksanforderungen

In diesem Verfahren wird beschrieben, wie Sie einen einfachen HID-Quelltreiber schreiben, der Headset-Tasten an das Betriebssystem meldet. In diesem Fall kann der Treiber, der diesen Code implementiert, ein vorhandener KMDF-Audiotreiber sein, der so geändert wurde, dass er als HID-Quelle fungiert, um Headset-Schaltflächen mithilfe von VHF zu melden.

  1. Schließen Sie Vhf.h ein, die im WDK für Windows 10 enthalten ist.

  2. Link zu vhfkm.lib, die im WDK enthalten ist.

  3. Erstellen Sie einen HID-Berichtsdeskriptor, den Ihr Gerät an das Betriebssystem melden möchte. In diesem Beispiel beschreibt der HID-Berichtsdeskriptor die Headset-Tasten. Der Bericht gibt einen HID-Eingabebericht mit der Größe 8 Bits (1 Byte) an. Die ersten drei Bits sind für die Mittleren Headset-, Lautstärke- und Leisertasten. Die restlichen Bits werden nicht verwendet.

    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
    };
    

Erstellen eines virtuellen HID-Geräts

Initialisieren Sie eine VHF_CONFIG-Struktur , indem Sie das makro VHF_CONFIG_INIT aufrufen und dann die VhfCreate-Methode aufrufen. Der Treiber muss VhfCreate bei PASSIVE_LEVEL nach dem WdfDeviceCreate-Aufruf in der Regel in der EvtDriverDeviceAdd-Rückruffunktion des Treibers aufrufen.

Im VhfCreate-Aufruf kann der Treiber bestimmte Konfigurationsoptionen angeben, z. B. Vorgänge, die asynchron verarbeitet werden müssen, oder Geräteinformationen (Hersteller-/Produkt-IDs) festlegen.

Beispielsweise fordert eine Anwendung einen TLC an. Wenn das HID-Klassentreiberpaar diese Anforderung empfängt, bestimmt das Paar den Typ der Anforderung und erstellt eine entsprechende HID Minidriver IOCTL-Anforderung und leitet sie an VHF weiter. Nach dem Abrufen der IOCTL-Anforderung kann VHF die Anforderung verarbeiten, sich auf den HID-Quelltreiber für die Verarbeitung verlassen oder die Anforderung mit STATUS_NOT_SUPPORTED abschließen.

VHF verarbeitet die folgenden IOCTLs:

Wenn die Anforderung GetFeature, SetFeature, WriteReport oder GetInputReport lautet und der HID-Quelltreiber eine entsprechende Rückruffunktion registriert hat, ruft VHF die Rückruffunktion auf. Innerhalb dieser Funktion kann der HID-Quelltreiber HID-Daten für das virtuelle HID-Gerät abrufen oder festlegen. Wenn der Treiber keinen Rückruf registriert, führt VHF die Anforderung mit status STATUS_NOT_SUPPORTED aus.

VHF ruft die vom HID-Quelltreiber implementierten Ereignisrückruffunktionen für diese IOCTLs auf:

Für alle anderen HID-Minidriver-IOCTL schließt VHF die Anforderung mit STATUS_NOT_SUPPORTED ab.

Das virtuelle HID-Gerät wird durch Aufrufen der VhfDelete gelöscht. Der EvtVhfCleanup-Rückruf ist erforderlich, wenn der Treiber Ressourcen für das virtuelle HID-Gerät zugewiesen hat. Der Treiber muss die EvtVhfCleanup-Funktion implementieren und einen Zeiger auf diese Funktion im EvtVhfCleanup-Member von VHF_CONFIG angeben. EvtVhfCleanup wird aufgerufen, bevor der VhfDelete-Aufruf abgeschlossen ist. Weitere Informationen finden Sie unter Löschen des virtuellen HID-Geräts.

Hinweis

Nach Abschluss eines asynchronen Vorgangs muss der Treiber VhfAsyncOperationComplete aufrufen, um die Ergebnisse des Vorgangs festzulegen. Sie können die Methode aus dem Ereignisrückruf oder zu einem späteren Zeitpunkt aufrufen, nachdem sie vom Rückruf zurückgegeben wurden.

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;
}

Übermitteln des HID-Eingabeberichts

Übermitteln Sie den HID-Eingabebericht, indem Sie VhfReadReportSubmit aufrufen.

In der Regel sendet ein HID-Gerät Informationen zu Zustandsänderungen, indem Eingabeberichte über Interrupts gesendet werden. Beispielsweise kann das Headsetgerät einen Bericht senden, wenn sich der Zustand einer Schaltfläche ändert. In einem solchen Ereignis wird die Interruptdienstroutine (ISR) des Treibers aufgerufen. In dieser Routine kann der Treiber einen verzögerten Prozeduraufruf (DPC) planen, der den Eingabebericht verarbeitet und an VHF übermittelt, der die Informationen an das Betriebssystem sendet. Standardmäßig puffert VHF den Bericht, und der HID-Quelltreiber kann mit der Übermittlung von HID-Eingabeberichten beginnen, sobald sie eingehen. Dadurch ist der HID-Quelltreiber nicht mehr erforderlich, um eine komplexe Synchronisierung zu implementieren.

Der HID-Quelltreiber kann Eingabeberichte übermitteln, indem die Pufferrichtlinie für ausstehende Berichte implementiert wird. Um doppelte Pufferung zu vermeiden, kann der HID-Quelltreiber die Rückruffunktion EvtVhfReadyForNextReadReport implementieren und nachverfolgen, ob VHF diesen Rückruf aufgerufen hat. Wenn er zuvor aufgerufen wurde, kann der HID-Quelltreiber VhfReadReportSubmit aufrufen, um einen Bericht zu übermitteln. Es muss warten, bis EvtVhfReadyForNextReadReport aufgerufen wird, bevor VhfReadReportSubmit erneut aufgerufen werden kann.

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);
    }
}

Löschen des virtuellen HID-Geräts

Löschen Sie das virtuelle HID-Gerät, indem Sie VhfDelete aufrufen.

VhfDelete kann synchron oder asynchron aufgerufen werden, indem der Wait-Parameter angegeben wird. Für einen synchronen Aufruf muss die -Methode bei PASSIVE_LEVEL aufgerufen werden, z. B. von EvtCleanupCallback des Geräteobjekts. VhfDelete wird nach dem Löschen des virtuellen HID-Geräts zurückgegeben. Wenn der Treiber VhfDelete asynchron aufruft, wird er sofort zurückgegeben, und VHF ruft EvtVhfCleanup auf, nachdem der Löschvorgang abgeschlossen ist. Die -Methode kann maximal DISPATCH_LEVEL aufgerufen werden. In diesem Fall muss der Treiber eine EvtVhfCleanup-Rückruffunktion registriert und implementiert haben, wenn er zuvor VhfCreate aufgerufen hat. Dies ist die Abfolge von Ereignissen, wenn der HID-Quelltreiber das virtuelle HID-Gerät löschen möchte:

  1. Der HID-Quelltreiber beendet das Initiieren von Aufrufen in VHF.
  2. Die HID-Quelle ruft VhfDelete auf, wobei Wait auf FALSE festgelegt ist.
  3. VHF beendet das Aufrufen von Rückruffunktionen, die vom HID-Quelltreiber implementiert wurden.
  4. VHF meldet das Gerät als nicht vorhanden an den PnP-Manager. An diesem Punkt kann der VhfDelete-Aufruf zurückgegeben werden.
  5. Wenn das Gerät als fehlendes Gerät gemeldet wird, ruft VHF EvtVhfCleanup auf , wenn der HID-Quelltreiber seine Implementierung registriert hat.
  6. Nachdem EvtVhfCleanup zurückgegeben wurde, führt VHF die Bereinigung durch.
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);
    }

}

Installieren des HID-Quelltreibers

Stellen Sie in der INF-Datei, die den HID-Quelltreiber installiert, sicher, dass Sie Vhf.sys mithilfe der AddReg-Direktive für Ihren HID-Quelltreiber als niedrigeren Filtertreiber deklarieren.

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

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