Freigeben über


Einen HID-Quelltreiber unter Verwendung des Virtual HID Framework (VHF) erstellen

In diesem Artikel wird folgendes erläutert:

  • Schreiben Sie einen Kernel-Mode Driver Framework (KMDF)HID-Quelltreiber, der HID-Leseberichte an Windows sendet.
  • Laden Sie den VHF-Treiber als niedrigeren Filter auf 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 erforderliche Maßnahmen ergreifen kann. Die Berichte befinden sich in Form von HID-Auflistungen und HID-Verwendungen. Das Gerät sendet diese Berichte über verschiedene Transporte, von denen einige Windows unterstützt, z. B. HID über I2C und HID über USB. In einigen Fällen unterstützt Windows den Transport nicht, oder die Berichte ordnen sich nicht direkt der echten Hardware zu. Es kann sich um einen Datenstrom im HID-Format handeln, den eine andere Softwarekomponente für virtuelle Hardware sendet, z. B. für Nicht-GPIO-Schaltflächen oder Sensoren. Ziehen Sie z. B. Beschleunigungsmesserdaten von einem Telefon in Betracht, das sich als Gamecontroller verhält und drahtlos an einen PC gesendet wird. In einem anderen Beispiel kann ein Computer Remoteeingaben von einem Miracast-Gerät mithilfe des UIBC-Protokolls empfangen.

In früheren Versionen von Windows mussten Sie zur Unterstützung neuer Transporte (reale Hardware oder Software) einen HID-Transport-Minidriver schreiben und an den von Microsoft bereitgestellten In-Box-Klassentreiber binden, Hidclass.sys. Das Klassen-/Minitreiberpaar stellt die HID-Sammlungen bereit, wie Top-Level Collections für Treiber höherer Ebene und Anwendungen im Benutzermodus. In diesem Modell war die Herausforderung das Schreiben eines Minitreibers, was eine komplexe Aufgabe sein kann.

Ab Windows 10 beseitigt das neue Virtual HID Framework (VHF) die Notwendigkeit, einen Transport-Minidriver zu schreiben. Stattdessen können Sie einen HID-Quelltreiber mithilfe von KMDF- oder WDM-Programmierschnittstellen schreiben. Das Framework besteht aus einer von Microsoft bereitgestellten statischen Bibliothek, die Programmierelemente verfügbar macht, die von Ihrem Treiber verwendet werden. Es enthält auch einen von Microsoft bereitgestellten In-box-Treiber, der ein oder mehrere untergeordnete Geräte enumeriert und dann mit dem Erstellen und Verwalten einer virtuellen HID-Struktur fortfährt.

Hinweis

In dieser Version unterstützt VHF einen HID-Quellgerätetreiber nur im Kernelmodus.

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

Virtuelle HID-Gerätestruktur

In dieser Abbildung zeigt der Gerätebaum die Treiber und die zugehörigen Geräteobjekte dar.

Diagramm einer virtuellen HID-Gerätestruktur.

HID-Quelltreiber (Ihr Treiber)

Der HID-Quelltreiber verweist auf Vhfkm.lib und enthält Vhf.h in seinem Buildprojekt. Der Treiber kann entweder mit dem Windows-Treibermodell (WDM ) oder Kernel-Mode Driver Framework (KMDF) geschrieben werden, das Teil von Windows Driver Frameworks (WDF) ist. Der Treiber kann als Filtertreiber oder Funktionstreiber im 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 von Microsoft bereitgestellter In-Box-Treiber. Dieser Treiber muss als niedriger Filtertreiber unterhalb Ihres Treibers im HID-Quellgerätestapel geladen werden. Der VHF-Treiber enumeriert Kindgeräte dynamisch und erstellt physische Geräteobjekte (Physical Device Objects, PDOs) für ein oder mehrere HID-Geräte, die Ihr HID-Quelltreiber spezifiziert. Außerdem wird die HID-Transport-Minitreiberfunktion der aufgezählten untergeordneten Geräte implementiert.

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

Das Hidclass/Mshidkmdf-Paar listet Top-Level Collections (TLC) auf, ähnlich wie diese Auflistungen für ein echtes HID-Gerät aufgezählt werden. 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 von 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 angegeben wird.

HID-Client (Anwendung)

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

Anforderungen an Header und Bibliotheken

In dieser Prozedur wird beschrieben, wie man einen HID-Quelltreiber schreibt, der die Kopfhörertasten an das Betriebssystem meldet. In diesem Fall kann der Treiber, der diesen Code implementiert, ein vorhandener geänderter KMDF-Audiotreiber sein, der als HID-Quelle fungiert und Headsetschaltflächen über VHF meldet.

  1. Schließen Sie Vhf.h aus dem WDK mit ein.

  2. Link zu vhfkm.lib im WDK enthalten.

  3. Erstellen Sie einen HID-Berichtsdeskriptor, den Ihr Gerät dem Betriebssystem melden möchte. In diesem Beispiel beschreibt der HID Report Descriptor die Headset-Schaltflächen. Der Bericht gibt einen HID-Eingabebericht an, größe 8 Bit (1 Byte). Die ersten drei Bits gelten für die Headset-Tasten mittel, lauter und leiser. Die verbleibenden 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 VHF_CONFIG_INIT Makro aufrufen und dann die VhfCreate-Methode aufrufen. Der Treiber muss VhfCreate bei PASSIVE_LEVEL aufrufen, typischerweise nachdem der WdfDeviceCreate-Aufruf in der EvtDriverDeviceAdd-Rückruffunktion des Treibers erfolgt.

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

Beispielsweise fordert eine Anwendung einen TLC an. Wenn das HID-Klassentreiberpaar diese Anforderung empfängt, bestimmt das Paar den Anforderungstyp und erstellt eine entsprechende HID Minidriver IOCTL-Anforderung und leitet sie an VHF weiter. Beim Erhalt der IOCTL-Anforderung kann VHF die Anforderung verarbeiten, sich auf den HID-Quelltreiber verlassen, um sie zu verarbeiten, oder die Anforderung mit STATUS_NOT_SUPPORTED abschließen.

VHF behandelt diese IOCTLs:

Wenn die Anforderung "GetFeature", "SetFeature", "WriteReport" oder " GetInputReport" ist und der HID-Quelltreiber eine entsprechende Rückruffunktion registriert hat, ruft VHF die Rückruffunktion auf. Innerhalb dieser Funktion kann der HID-Quelltreiber HID-Quelldaten für das virtuelle HID-Gerät abrufen oder festlegen. Wenn der Treiber keinen Rückruf registriert, schließt VHF die Anforderung mit Status STATUS_NOT_SUPPORTED ab.

VHF ruft die von HID-Quelltreibern 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-Element 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 direkt im Ereignis-Callback oder zu einem späteren Zeitpunkt nach der Rückkehr aus dem Callback aufrufen.

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

Den HID-Eingabebericht übermitteln

Senden Sie den HID-Eingabebericht durch Aufrufen von VhfReadReportSubmit.

In der Regel sendet ein HID-Gerät Informationen über Zustandsänderungen, indem Eingabeberichte durch Unterbrechungen gesendet werden. Beispielsweise kann das Headsetgerät einen Bericht senden, wenn sich der Zustand einer Schaltfläche ändert. In einem solchen Ereignis wird die Interrupt-Dienstroutine (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, sodass der HID-Quelltreiber beginnen kann, HID-Eingabeberichte einzureichen, sobald sie eingehen. Diese Pufferung beseitigt die Notwendigkeit, dass der HID-Quelltreiber komplexe Synchronisierung implementiert.

Der HID-Quelltreiber kann Eingabeberichte übermitteln, indem er die Pufferrichtlinie für ausstehende Berichte implementiert. Um doppelte Zwischenspeicherung zu vermeiden, kann der HID-Quelltreiber die Rückruffunktion EvtVhfReadyForNextReadReport implementieren und nachverfolgen, ob VHF diese Rückruffunktion aufgerufen hat. Wenn sie zuvor aufgerufen wurde, kann der HID-Quelltreiber VhfReadReportSubmit aufrufen, um einen Bericht zu übermitteln. Es muss warten, bis EvtVhfReadyForNextReadReport aufgerufen wird, bevor er VhfReadReportSubmit erneut aufrufen 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. Bei einem synchronen Aufruf muss die Methode bei PASSIVE_LEVEL aufgerufen werden, z. B. aus EvtCleanupCallback des Geräteobjekts. VhfDelete kehrt zurück, nachdem das virtuelle HID-Gerät gelöscht wurde. Wenn der Treiber VhfDelete asynchron aufruft, wird es sofort zurückgegeben, und VHF ruft EvtVhfCleanup nach Abschluss des Löschvorgangs auf. Die Methode kann bis zu DISPATCH_LEVEL aufgerufen werden. In diesem Fall musste der Treiber eine EvtVhfCleanup-Rückruffunktion registrieren und implementieren, 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 stoppt das Initiieren von Anrufen in VHF.
  2. HID-Quelle ruft VhfDelete auf, wobei Wait auf FALSE festgelegt ist.
  3. VHF stoppt das Aufrufen von Rückruffunktionen, die vom HID-Quelltreiber implementiert werden.
  4. VHF beginnt damit, das Gerät dem PnP-Manager als vermisst zu melden. An diesem Punkt könnte der VhfDelete-Aufruf zurückkehren.
  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ückkehrt, führt VHF seine 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 als niedrigeren Filtertreiber für Ihren HID-Quelltreiber deklarieren.

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

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