Verwenden des fortlaufenden Readers zum Lesen von Daten aus einer USB-Pipe

In diesem Thema wird das von WDF bereitgestellte Continuous Reader-Objekt beschrieben. Die Verfahren in diesem Thema enthalten schritt-für-Schritt-Anweisungen zum Konfigurieren des Objekts und zum Lesen von Daten aus einer USB-Pipe.

Windows Driver Framework (WDF) stellt ein spezialisiertes Objekt bereit, das als fortlaufender Reader bezeichnet wird. Dieses Objekt ermöglicht es einem USB-Clienttreiber, Daten aus Massendaten zu lesen und Endpunkte kontinuierlich zu unterbrechen, solange Daten verfügbar sind. Um den Reader verwenden zu können, muss der Clienttreiber über ein Handle für ein USB-Zielpipeobjekt verfügen, das dem Endpunkt zugeordnet ist, von dem der Treiber Daten liest. Der Endpunkt muss sich in der aktiven Konfiguration enthalten. Sie können eine Konfiguration auf zwei Arten aktivieren: indem Sie eine USB-Konfiguration auswählen oder die alternative Einstellung in der aktuellen Konfiguration ändern. Weitere Informationen zu diesen Vorgängen finden Sie unter Auswählen einer Konfiguration für ein USB-Gerät und Auswählen einer alternativen Einstellung in einer USB-Schnittstelle.

Nach dem Erstellen des fortlaufenden Readers kann der Clienttreiber den Reader bei Bedarf starten und beenden. Der fortlaufende Reader, der sicherstellt, dass eine Leseanforderung für das Zielpipeobjekt immer verfügbar ist und der Clienttreiber immer bereit ist, Daten vom Endpunkt zu empfangen.

Der fortlaufende Reader wird nicht automatisch vom Framework verwaltet. Dies bedeutet, dass der Clienttreiber den Reader beenden muss, wenn das Gerät in einen niedrigeren Leistungszustand wechselt, und den Reader neu starten muss, wenn das Gerät in den Betriebszustand wechselt.

In diesem Artikel werden Folgendes verwendet:

Vorbereitung

Bevor der Clienttreiber den fortlaufenden Reader verwenden kann, stellen Sie sicher, dass diese Anforderungen erfüllt sind:

  • Ihr USB-Gerät muss über einen IN-Endpunkt verfügen. Überprüfen Sie die Gerätekonfiguration in USBView. Usbview.exe ist eine Anwendung, mit der Sie alle USB-Controller und die angeschlossenen USB-Geräte durchsuchen können. Normalerweise wird USBView im Ordner Debugger im Windows Driver Kit (WDK) installiert.

  • Der Clienttreiber muss das Framework-USB-Zielgerätobjekt erstellt haben.

    Wenn Sie die USB-Vorlagen verwenden, die mit Microsoft Visual Studio Professional 2012 bereitgestellt werden, führt der Vorlagencode diese Aufgaben aus. Der Vorlagencode ruft das Handle für das Zielgerätobjekt ab und speichert im Gerätekontext.

    KMDF-Clienttreiber:

    Ein KMDF-Clienttreiber muss ein WDFUSBDEVICE-Handle abrufen, indem er die WdfUsbTargetDeviceCreateWithParameters-Methode aufruft . Weitere Informationen finden Sie unter "Gerätequellcode" unter Grundlegendes zur USB-Clienttreibercodestruktur (KMDF).

    UMDF-Clienttreiber:

    Ein UMDF-Clienttreiber muss einen IWDFUsbTargetDevice-Zeiger abrufen, indem er das Framework-Zielgerätobjekt abfragt. Weitere Informationen finden Sie unter "IPnpCallbackHardware-Implementierung und USB-spezifische Aufgaben" in Grundlegendes zur STRUKTUR des USB-Clienttreibercodes (UMDF).

  • Das Gerät muss über eine aktive Konfiguration verfügen.

    Wenn Sie USB-Vorlagen verwenden, wählt der Code die erste Konfiguration und die standardmäßige alternative Einstellung in jeder Schnittstelle aus. Informationen zum Ändern der alternativen Einstellung finden Sie unter Auswählen einer alternativen Einstellung in einer USB-Schnittstelle.

    KMDF-Clienttreiber:

    Ein KMDF-Clienttreiber muss die WdfUsbTargetDeviceSelectConfig-Methode aufrufen.

    UMDF-Clienttreiber:

    Für einen UMDF-Clienttreiber wählt das Framework die erste Konfiguration und die standardmäßige alternative Einstellung für jede Schnittstelle in dieser Konfiguration aus.

  • Der Clienttreiber muss über ein Handle für das Framework-Zielpipeobjekt für den IN-Endpunkt verfügen. Weitere Informationen finden Sie unter Auflisten von USB-Rohren.

Verwenden des fortlaufenden Readers in einem KMDF-Clienttreiber

Bevor Sie mit der Verwendung des fortlaufenden Readers beginnen, müssen Sie ihn konfigurieren, indem Sie eine WDF_USB_CONTINUOUS_READER_CONFIG-Struktur initialisieren.

Konfigurieren des fortlaufenden Readers in einem KMDF-Clienttreiber

  1. Initialisieren Sie eine WDF_USB_CONTINUOUS_READER_CONFIG-Struktur , indem Sie das makro WDF_USB_CONTINUOUS_READER_CONFIG_INIT aufrufen.

  2. Geben Sie die Konfigurationsoptionen in der WDF_USB_CONTINUOUS_READER_CONFIG-Struktur an.

  3. Rufen Sie die WdfUsbTargetPipeConfigContinuousReader-Methode auf.

    Im folgenden Beispielcode wird der fortlaufende Reader für das angegebene Zielpipeobjekt konfiguriert.

    NTSTATUS FX3ConfigureContinuousReader(
        _In_ WDFDEVICE Device,
        _In_ WDFUSBPIPE Pipe)
    {
        NTSTATUS status;
        PDEVICE_CONTEXT                     pDeviceContext;
        WDF_USB_CONTINUOUS_READER_CONFIG    readerConfig;
        PPIPE_CONTEXT                       pipeContext;
    
        PAGED_CODE();
    
        pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
        pipeContext = GetPipeContext (Pipe);
    
        WDF_USB_CONTINUOUS_READER_CONFIG_INIT(
            &readerConfig,
            FX3EvtReadComplete,
            pDeviceContext,
            pipeContext->MaxPacketSize);
    
        readerConfig.EvtUsbTargetPipeReadersFailed=FX3EvtReadFailed;
    
        status = WdfUsbTargetPipeConfigContinuousReader(
            Pipe,
            &readerConfig);
    
        if (!NT_SUCCESS (status))
        {
            TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
                "%!FUNC! WdfUsbTargetPipeConfigContinuousReader failed 0x%x", status);
    
            goto Exit;
        }
    
    Exit:
        return status;
    }
    

In der Regel konfiguriert der Clienttreiber den fortlaufenden Reader in der Rückruffunktion EvtDevicePrepareHardware , nachdem die Zielpipeobjekte in der aktiven Einstellung aufgelistet wurden.

Im vorherigen Beispiel gibt der Clienttreiber seine Konfigurationsoptionen auf zwei Arten an. Rufen Sie zuerst WDF_USB_CONTINUOUS_READER_CONFIG_INIT auf, und legen Sie dann WDF_USB_CONTINUOUS_READER_CONFIG-Member fest. Beachten Sie die Parameter für WDF_USB_CONTINUOUS_READER_CONFIG_INIT. Diese Werte sind obligatorisch. In diesem Beispiel gibt der Clienttreiber Folgendes an:

  • Ein Zeiger auf eine Vervollständigungsroutine, die der Treiber implementiert. Das Framework ruft diese Routine auf, wenn eine Leseanforderung abgeschlossen wird. In der Vervollständigungsroutine kann der Treiber auf den Speicherort zugreifen, der die gelesenen Daten enthält. Die Implementierung der Abschlussroutine wird in Schritt 2 erläutert.
  • Ein Zeiger auf den vom Treiber definierten Kontext.
  • Die Anzahl der Bytes, die in einer einzelnen Übertragung vom Gerät gelesen werden können. Der Clienttreiber kann diese Informationen in einer WDF_USB_PIPE_INFORMATION-Struktur abrufen, indem er die WdfUsbInterfaceGetConfiguredPipe- oder WdfUsbTargetPipeGetInformation-Methode aufruft . Weitere Informationen finden Sie unter Auflisten von USB-Rohren.

WDF_USB_CONTINUOUS_READER_CONFIG_INIT konfiguriert den fortlaufenden Reader so, dass der Standardwert für NumPendingReads verwendet wird. Dieser Wert bestimmt die Anzahl der Leseanforderungen, die das Framework der ausstehenden Warteschlange hinzufügt. Der Standardwert wurde so festgelegt, dass für viele Geräte in vielen Prozessorkonfigurationen eine einigermaßen gute Leistung bereitgestellt wird.

Zusätzlich zu den in WDF_USB_CONTINUOUS_READER_CONFIG_INIT angegebenen Konfigurationsparametern wird im Beispiel auch eine Fehlerroutine in WDF_USB_CONTINUOUS_READER_CONFIG festgelegt. Diese Fehlerroutine ist optional.

Zusätzlich zur Fehlerroutine gibt es weitere Member in WDF_USB_CONTINUOUS_READER_CONFIG , mit denen der Clienttreiber das Layout des Übertragungspuffers angeben kann. Betrachten Sie beispielsweise einen Netzwerktreiber, der den fortlaufenden Reader zum Empfangen von Netzwerkpaketen verwendet. Jedes Paket enthält Header-, Nutzlast- und Fußzeilendaten. Um das Paket zu beschreiben, muss der Treiber zunächst die Größe des Pakets in seinem Aufruf von WDF_USB_CONTINUOUS_READER_CONFIG_INIT angeben. Anschließend muss der Treiber die Länge der Kopf- und Fußzeile angeben, indem er die Member HeaderLength und TrailerLengthvon WDF_USB_CONTINUOUS_READER_CONFIG. Das Framework verwendet diese Werte, um die Byteoffsets auf beiden Seiten der Nutzlast zu berechnen. Wenn Nutzlastdaten vom Endpunkt gelesen werden, speichert das Framework diese Daten im Teil des Puffers zwischen den Offsets.

Implementieren der Vervollständigungsroutine

Das Framework ruft die vom Client-Treiber implementierte Vervollständigungsroutine bei jedem Abschluss einer Anforderung auf. Das Framework übergibt die Anzahl der gelesenen Bytes und ein WDFMEMORY-Objekt, dessen Puffer die Daten enthält, die aus der Pipe gelesen werden.

Der folgende Beispielcode zeigt die Implementierung der Abschlussroutine.

EVT_WDF_USB_READER_COMPLETION_ROUTINE FX3EvtReadComplete;

VOID FX3EvtReadComplete(
    __in  WDFUSBPIPE Pipe,
    __in  WDFMEMORY Buffer,
    __in  size_t NumBytesTransferred,
    __in  WDFCONTEXT Context
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    PVOID  requestBuffer;

    pDeviceContext = (PDEVICE_CONTEXT)Context;

    if (NumBytesTransferred == 0)
    {
        return;
    }

    requestBuffer = WdfMemoryGetBuffer(Buffer, NULL);

    if (Pipe == pDeviceContext->InterruptPipe)
    {
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,
                                "Interrupt endpoint: %s.\n",
                                requestBuffer ));
    }

    return;
}

Das Framework ruft die vom Client-Treiber implementierte Vervollständigungsroutine bei jedem Abschluss einer Anforderung auf. Das Framework weist jedem Lesevorgang ein Speicherobjekt zu. In der Vervollständigungsroutine übergibt das Framework die Anzahl der gelesenen Bytes und ein WDFMEMORY-Handle an das Speicherobjekt. Der Speicherobjektpuffer enthält die Daten, die aus der Pipe gelesen werden. Der Clienttreiber darf das Speicherobjekt nicht freigeben. Das Framework gibt das -Objekt frei, nachdem jede Vervollständigungsroutine zurückgegeben wird. Wenn der Clienttreiber die empfangenen Daten speichern möchte, muss der Treiber den Inhalt des Puffers in der Vervollständigungsroutine kopieren.

Implementieren der Fehlerroutine

Das Framework ruft die vom Client-Treiber implementierte Fehlerroutine auf, um den Treiber darüber zu informieren, dass der fortlaufende Reader bei der Verarbeitung einer Leseanforderung einen Fehler gemeldet hat. Das Framework übergibt den Zeiger an das Zielpipeobjekt, für das die Anforderung fehlgeschlagen ist, und fehlercodewerte. Basierend auf diesen Fehlercodewerten kann der Treiber seinen Fehlerwiederherstellungsmechanismus implementieren. Der Treiber muss auch einen geeigneten Wert zurückgeben, der dem Framework angibt, ob das Framework den fortlaufenden Reader neu starten soll.

Der folgende Beispielcode zeigt eine Fehlerroutineimplementierung.

EVT_WDF_USB_READERS_FAILED FX3EvtReadFailed;

BOOLEAN
FX3EvtReadFailed(
    WDFUSBPIPE      Pipe,
    NTSTATUS        Status,
    USBD_STATUS     UsbdStatus
    )
{
    UNREFERENCED_PARAMETER(Status);

    TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! ReadersFailedCallback failed NTSTATUS 0x%x, UsbdStatus 0x%x\n",
                    status,
                    UsbdStatus);

    return TRUE;
}

Im vorherigen Beispiel gibt der Treiber TRUE zurück. Dieser Wert gibt dem Framework an, dass es die Pipe zurücksetzen und dann den fortlaufenden Reader neu starten muss.

Alternativ kann der Clienttreiber FALSE zurückgeben und einen Fehlerwiederherstellungsmechanismus bereitstellen, wenn eine Stockbedingung für die Pipe auftritt. Der Treiber kann z. B. die USBD-status überprüfen und eine Reset-Pipe-Anforderung ausgeben, um die Stockbedingung zu löschen.

Informationen zur Fehlerwiederherstellung in Pipes finden Sie unter Wiederherstellen von USB-Pipefehlern.

Starten und Beenden des fortlaufenden Readers

Weisen Sie das Framework an, den fortlaufenden Reader zu starten, wenn das Gerät in den Betriebszustand wechselt. beenden Sie den Reader, wenn das Gerät den Arbeitszustand verlässt. Rufen Sie diese Methoden auf, und geben Sie das Zielpipelineobjekt als E/A-Zielobjekt an.

Der fortlaufende Reader wird nicht automatisch vom Framework verwaltet. Daher muss der Clienttreiber das Zielpipeobjekt explizit starten oder beenden, wenn sich der Energiezustand des Geräts ändert. Der Treiber ruft WdfIoTargetStart in der EvtDeviceD0Entry-Implementierung des Treibers auf. Mit diesem Aufruf wird sichergestellt, dass die Warteschlange Anforderungen nur dann übermittelt, wenn sich das Gerät im Betriebszustand befindet. Umgekehrt ruft der Treiber WdfIoTargetStop in der EvtDeviceD0Exit-Implementierung des Treibers auf, sodass die Warteschlange keine Anforderungen mehr sendet, wenn das Gerät in einen niedrigeren Leistungszustand wechselt.

Im folgenden Beispielcode wird der fortlaufende Reader für das angegebene Zielpipeobjekt konfiguriert.

EVT_WDF_DEVICE_D0_ENTRY FX3EvtDeviceD0Entry;

NTSTATUS FX3EvtDeviceD0Entry(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE PreviousState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;

    PAGED_CODE();

    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    status = WdfIoTargetStart (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe));

    if (!NT_SUCCESS (status))
    {
        TraceEvents(TRACE_LEVEL_ERROR, TRACE_DEVICE,
            "%!FUNC! Could not start interrupt pipe failed 0x%x", status);
    }
}

EVT_WDF_DEVICE_D0_EXIT FX3EvtDeviceD0Exit;

NTSTATUS FX3EvtDeviceD0Exit(
    __in  WDFDEVICE Device,
    __in  WDF_POWER_DEVICE_STATE TargetState
    )
{
    PDEVICE_CONTEXT  pDeviceContext;
    NTSTATUS status;
    PAGED_CODE();
    pDeviceContext = WdfObjectGet_DEVICE_CONTEXT(Device);
    WdfIoTargetStop (WdfUsbTargetPipeGetIoTarget (pDeviceContext->InterruptPipe), WdfIoTargetCancelSentIo));
}

Das vorherige Beispiel zeigt die Implementierung für Die Rückrufroutinen EvtDeviceD0Entry und EvtDeviceD0Exit . Mit dem Action-Parameter von WdfIoTargetStop kann der Clienttreiber die Aktion für die ausstehenden Anforderungen in der Warteschlange entscheiden, wenn das Gerät den Arbeitszustand verlässt. Im Beispiel gibt der Treiber WdfIoTargetCancelSentIo an. Diese Option weist das Framework an, alle ausstehenden Anforderungen in der Warteschlange abzubrechen. Alternativ kann der Treiber das Framework anweisen, auf den Abschluss ausstehender Anforderungen zu warten, bevor das E/A-Ziel beendet wird, oder die ausstehenden Anforderungen beibehalten und fortgesetzt werden, wenn das E/A-Ziel neu gestartet wird.

Verwenden des fortlaufenden Lesegeräts in einem UMDF-Clienttreiber

Bevor Sie den kontinuierlichen Reader verwenden, müssen Sie den Reader in Ihrer Implementierung der IPnpCallbackHardware::OnPrepareHardware-Methode konfigurieren. Nachdem Sie einen Zeiger auf die IWDFUsbTargetPipe-Schnittstelle des Zielpipeobjekts erhalten haben, das dem IN-Endpunkt zugeordnet ist, führen Sie die folgenden Schritte aus:

Konfigurieren des fortlaufenden Readers in einem UMDF-Clienttreiber

  1. Rufen Sie QueryInterface für das Zielpipeobjekt (IWDFUsbTargetPipe) auf, und rufen Sie die IWDFUsbTargetPipe2-Schnittstelle ab.

  2. Rufen Sie QueryInterface für das Geräterückrufobjekt auf, und fragen Sie die IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstelle ab. Um den kontinuierlichen Reader verwenden zu können, müssen Sie IUsbTargetPipeContinuousReaderCallbackReadComplete implementieren. Die Implementierung wird weiter unten in diesem Thema beschrieben.

  3. Rufen Sie QueryInterface für das Geräterückrufobjekt auf, und fragen Sie die IUsbTargetPipeContinuousReaderCallbackReadersFailed-Schnittstelle ab, wenn Sie einen Fehlerrückruf implementiert haben. Die Implementierung wird weiter unten in diesem Thema beschrieben.

  4. Rufen Sie die IWDFUsbTargetPipe2::ConfigureContinuousReader-Methode auf, und geben Sie die Konfigurationsparameter an, z. B. Header, Trailer, Anzahl der ausstehenden Anforderungen und Verweise auf die Rückrufmethoden für Vervollständigung und Fehler.

    Die -Methode konfiguriert den kontinuierlichen Reader für das Zielpipeobjekt. Der fortlaufende Reader erstellt Warteschlangen, die eine Reihe von Leseanforderungen verwalten, während sie vom Zielpipelineobjekt gesendet und empfangen werden.

Im folgenden Beispielcode wird der fortlaufende Reader für das angegebene Zielpipeobjekt konfiguriert. Im Beispiel wird davon ausgegangen, dass das vom Aufrufer angegebene Zielpipeobjekt einem IN-Endpunkt zugeordnet ist. Der fortlaufende Reader ist so konfiguriert, dass USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE Bytes gelesen werden. , um die Standardanzahl ausstehender Anforderungen zu verwenden, die vom Framework verwendet werden; , um die vom Clienttreiber bereitgestellten Vervollständigungs- und Fehlerrückrufmethoden aufzurufen. Der empfangene Puffer enthält keine Header- oder Trailerdaten.

HRESULT CDeviceCallback::ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe)
{
    if (!pFxPipe)
    {
        return E_INVALIDARG;
    }

    IUsbTargetPipeContinuousReaderCallbackReadComplete *pOnCompletionCallback = NULL;
    IUsbTargetPipeContinuousReaderCallbackReadersFailed *pOnFailureCallback = NULL;
    IWDFUsbTargetPipe2* pFxUsbPipe2 = NULL;

    HRESULT hr = S_OK;

    // Set up the continuous reader to read from the target pipe object.

    //Get a pointer to the target pipe2 object.
    hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&pFxUsbPipe2));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the completion callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnCompletionCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the failure callback.
    hr = QueryInterface(IID_PPV_ARGS(&pOnFailureCallback));
    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

    //Get a pointer to the target pipe2 object.
    hr = pFxUsbPipe2->ConfigureContinuousReader (
        USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE, //size of data to be read
        0, //Header
        0, //Trailer
        0, // Number of pending requests queued by WDF
        NULL, // Cleanup callback. Not provided.
        pOnCompletionCallback, //Completion routine.
        NULL, //Completion routine context. Not provided.
        pOnFailureCallback); //Failure routine. Not provided

    if (FAILED(hr))
    {
        goto ConfigureContinuousReaderExit;
    }

ConfigureContinuousReaderExit:

    if (pOnFailureCallback)
    {
        pOnFailureCallback->Release();
        pOnFailureCallback = NULL;
    }

    if (pOnCompletionCallback)
    {
        pOnCompletionCallback->Release();
        pOnCompletionCallback = NULL;
    }

    if (pFxUsbPipe2)
    {
        pFxUsbPipe2->Release();
        pFxUsbPipe2 = NULL;
    }

    return hr;
}

Geben Sie als Nächstes den Zustand des Zielpipeobjekts an, wenn das Gerät in einen Arbeitszustand (D0) wechselt und beendet.

Wenn ein Clienttreiber eine stromverwaltete Warteschlange zum Senden von Anforderungen an eine Pipe verwendet, übermittelt die Warteschlange Anforderungen nur, wenn sich das Gerät im Zustand D0 befindet. Wenn der Energiezustand des Geräts von D0 in einen niedrigeren Leistungszustand wechselt (bei D0-Exit ), schließt das Zielpipeobjekt die ausstehenden Anforderungen ab, und die Warteschlange beendet die Übermittlung von Anforderungen an das Zielpipeobjekt. Daher ist der Clienttreiber nicht erforderlich, um das Zielpipeobjekt zu starten und zu beenden.

Der fortlaufende Reader verwendet keine vom Strom verwalteten Warteschlangen, um Anforderungen zu übermitteln. Daher müssen Sie das Zielpipeeobjekt explizit starten oder beenden, wenn sich der Energiezustand des Geräts ändert. Zum Ändern des Zustands des Zielpipeeobjekts können Sie die vom Framework implementierte IWDFIoTargetStateManagement-Schnittstelle verwenden. Nachdem Sie einen Zeiger auf die IWDFUsbTargetPipe-Schnittstelle des Zielpipeobjekts erhalten haben, das dem IN-Endpunkt zugeordnet ist, führen Sie die folgenden Schritte aus:

Implementieren der Zustandsverwaltung

  1. Rufen Sie in Ihrer Implementierung von IPnpCallbackHardware::OnPrepareHardwareQueryInterface für das Zielpipeobjekt (IWDFUsbTargetPipe) auf, und rufen Sie die IWDFIoTargetStateManagement-Schnittstelle ab. Speichern Sie den Verweis in einer Membervariablen Ihrer Geräterückrufklasse.

  2. Implementieren Sie die IPnpCallback-Schnittstelle für das Geräterückrufobjekt.

  3. Rufen Sie in der Implementierung der IPnpCallback::OnD0Entry-MethodeIWDFIoTargetStateManagement::Start auf, um den fortlaufenden Reader zu starten.

  4. Rufen Sie in der Implementierung der IPnpCallback::OnD0Exit-MethodeIWDFIoTargetStateManagement::Stop auf, um den fortlaufenden Reader zu beenden.

Nachdem das Gerät in einen Arbeitszustand (D0) versetzt wurde, ruft das Framework die vom Clienttreiber angegebene D0-entry-Rückrufmethode auf, die das Zielpipeobjekt startet. Wenn das Gerät den D0-Zustand verlässt, ruft das Framework die Rückrufmethode D0-exit auf. Das Zielpipeeobjekt schließt die Anzahl der ausstehenden Leseanforderungen ab, die vom Clienttreiber konfiguriert wurden, und beendet die Annahme neuer Anforderungen. Der folgende Beispielcode implementiert die IPnpCallback-Schnittstelle für das Geräterückrufobjekt.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

Der folgende Beispielcode zeigt, wie Sie einen Zeiger auf die IWDFIoTargetStateManagement-Schnittstelle des Zielpipeobjekts in der IPnpCallback::OnPrepareHardware-Methode abrufen.

   //Enumerate the endpoints and get the interrupt pipe.
    for (UCHAR index = 0; index < NumEndpoints; index++)
    {
        hr = pFxInterface->RetrieveUsbPipeObject(index, &pFxPipe);

        if (SUCCEEDED (hr) && pFxPipe)
        {
            if ((pFxPipe->IsInEndPoint()) && (pFxPipe->GetType()==UsbdPipeTypeInterrupt))
            {
                //Pipe is for an interrupt IN endpoint.
                hr = pFxPipe->QueryInterface(IID_PPV_ARGS(&m_pFxIoTargetInterruptPipeStateMgmt));

                if (m_pFxIoTargetInterruptPipeStateMgmt)
                {
                    m_pFxUsbPipe = pFxPipe;
                    break;
                }

            }
            else
            {
                //Pipe is NOT for an interrupt IN endpoint.
                pFxPipe->Release();
                pFxPipe = NULL;
            }
        }
        else
        {
             //Pipe not found.
        }
    }

Der folgende Beispielcode zeigt, wie Sie einen Zeiger auf die IWDFIoTargetStateManagement-Schnittstelle des Zielpipeobjekts in der IPnpCallbackHardware::OnPrepareHardware-Methode abrufen.

 HRESULT CDeviceCallback::OnD0Entry(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{

    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    HRESULT hr = m_pFxIoTargetInterruptPipeStateMgmt->Start();

    if (FAILED (hr))
    {
        goto OnD0EntryExit;
    }

OnD0EntryExit:
    return hr;
}

HRESULT CDeviceCallback::OnD0Exit(
    IWDFDevice*  pWdfDevice,
    WDF_POWER_DEVICE_STATE  previousState
    )
{
    if (!m_pFxIoTargetInterruptPipeStateMgmt)
    {
        return E_FAIL;
    }

    // Stop the I/O target always succeeds.
    (void)m_pFxIoTargetInterruptPipeStateMgmt->Stop(WdfIoTargetCancelSentIo);

    return S_OK;
}

Nachdem der fortlaufende Reader eine Leseanforderung abgeschlossen hat, muss der Clienttreiber eine Möglichkeit bieten, benachrichtigt zu werden, wenn die Anforderung eine Leseanforderung erfolgreich abgeschlossen hat. Der Clienttreiber muss diesen Code dem Geräterückrufobjekt hinzufügen.

Bereitstellen eines Abschlussrückrufs durch Implementieren von IUsbTargetPipeContinuousReaderCallbackReadComplete

  1. Implementieren Sie die IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstelle auf dem Geräterückrufobjekt.

  2. Stellen Sie sicher, dass die QueryInterface-Implementierung des Geräterückrufobjekts die Verweisanzahl des Rückrufobjekts erhöht und dann den IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstellenzeiger zurückgibt.

  3. Greifen Sie in der Implementierung der IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion-Methode auf die gelesenen Daten zu, die aus der Pipe gelesen wurden. Der pMemory-Parameter verweist auf den Arbeitsspeicher, der vom Framework zugewiesen wird, das die Daten enthält. Sie können IWDFMemory::GetDataBuffer aufrufen, um den Puffer abzurufen, der die Daten enthält. Der Puffer enthält den Header, aber die Länge der Daten, die durch den NumBytesTransferred-Parameter von OnReaderCompletion angegeben wird, enthält nicht die Headerlänge. Die Headerlänge wird vom Clienttreiber angegeben, während der fortlaufende Reader im Aufruf des Treibers von IWDFUsbTargetPipe2::ConfigureContinuousReader konfiguriert wird.

  4. Geben Sie einen Zeiger auf den Abschlussrückruf im pOnCompletion-Parameter der IWDFUsbTargetPipe2::ConfigureContinuousReader-Methode an.

Jedes Mal, wenn diese Daten auf dem Endpunkt auf dem Gerät verfügbar sind, schließt das Zielpipeobjekt eine Leseanforderung ab. Wenn die Leseanforderung erfolgreich abgeschlossen wurde, benachrichtigt das Framework den Clienttreiber durch Aufrufen von IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion. Andernfalls ruft das Framework einen vom Clienttreiber bereitgestellten Fehlerrückruf auf, wenn das Zielpipeobjekt einen Fehler für die Leseanforderung meldet.

Der folgende Beispielcode implementiert die IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstelle für das Geräterückrufobjekt.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete

{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

Der folgende Beispielcode zeigt die QueryInterface-Implementierung des Geräterückrufobjekts.

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

Der folgende Beispielcode zeigt, wie Sie Daten aus dem Puffer abrufen, der von IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion zurückgegeben wird. Jedes Mal, wenn das Zielpipeobjekt eine Leseanforderung erfolgreich abgeschlossen hat, ruft das Framework OnReaderCompletion auf. Das Beispiel ruft den Puffer ab, der Daten enthält, und gibt den Inhalt in der Debuggerausgabe aus.

 VOID CDeviceCallback::OnReaderCompletion(
    IWDFUsbTargetPipe* pPipe,
    IWDFMemory* pMemory,
    SIZE_T NumBytesTransferred,
    PVOID Context)
{
    if (pPipe != m_pFxUsbInterruptPipe)
    {
        return;
    }

    if (NumBytesTransferred == 0)
    {
        // NumBytesTransferred is zero.
        return;
    }

    PVOID pBuff = NULL;
    LONG CurrentData = 0;
    char data[20];

    pBuff = pMemory->GetDataBuffer(NULL);

    if (pBuff)
    {
        CopyMemory(&CurrentData, pBuff, sizeof(CurrentData));
        sprintf_s(data, 20, "%d\n", CurrentData);
        OutputDebugString(data);
        pBuff = NULL;
    }
    else
    {
        OutputDebugString(TEXT("Unable to get data buffer."));
    }
}

Der Clienttreiber kann Benachrichtigungen vom Framework erhalten, wenn ein Fehler im Zielpipeobjekt auftritt, während eine Leseanforderung abgeschlossen wird. Um Benachrichtigungen zu erhalten, muss der Clienttreiber einen Fehlerrückruf implementieren und einen Zeiger auf den Rückruf bereitstellen, während der fortlaufende Reader konfiguriert wird. Im folgenden Verfahren wird beschrieben, wie Sie den Fehlerrückruf implementieren.

Bereitstellen eines Fehlerrückrufs durch Implementieren von IUsbTargetPipeContinuousReaderCallbackReadersFailed

  1. Implementieren Sie die IUsbTargetPipeContinuousReaderCallbackReadersFailed-Schnittstelle auf dem Geräterückrufobjekt.

  2. Stellen Sie sicher, dass die QueryInterface-Implementierung des Geräterückrufobjekts die Verweisanzahl des Rückrufobjekts erhöht und dann den IUsbTargetPipeContinuousReaderCallbackReadersFailed-Schnittstellenzeiger zurückgibt.

  3. Stellen Sie in der Implementierung der IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure-Methode die Fehlerbehandlung der fehlerhaften Leseanforderung bereit.

    Wenn der fortlaufende Reader eine Leseanforderung nicht erfüllt und der Clienttreiber einen Fehlerrückruf bereitstellt, ruft das Framework die IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure-Methode auf. Das Framework stellt einen HRESULT-Wert im hrStatus-Parameter bereit, der den Fehlercode angibt, der im Zielpipelineobjekt aufgetreten ist. Basierend auf diesem Fehlercode können Sie eine bestimmte Fehlerbehandlung bereitstellen. Wenn sie beispielsweise möchten, dass das Framework die Pipe zurücksetzt und dann den fortlaufenden Reader neu startet, stellen Sie sicher, dass der Rückruf TRUE zurückgibt.

    Hinweis Rufen Sie nicht IWDFIoTargetStateManagement::Start und IWDFIoTargetStateManagement::Stop innerhalb des Fehlerrückrufs auf.

  4. Geben Sie einen Zeiger auf den Fehlerrückruf im pOnFailure-Parameter der IWDFUsbTargetPipe2::ConfigureContinuousReader-Methode an.

Der folgende Beispielcode implementiert die IUsbTargetPipeContinuousReaderCallbackReadersFailed-Schnittstelle für das Geräterückrufobjekt.

class CDeviceCallback :
    public IPnpCallbackHardware,
    public IPnpCallback,
    public IUsbTargetPipeContinuousReaderCallbackReadComplete,
    public IUsbTargetPipeContinuousReaderCallbackReadersFailed
{
public:
    CDeviceCallback();
    ~CDeviceCallback();
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID** ppvObject);
    virtual ULONG STDMETHODCALLTYPE AddRef();
    virtual ULONG STDMETHODCALLTYPE Release();

    virtual HRESULT STDMETHODCALLTYPE OnPrepareHardware(IWDFDevice* pDevice);
    virtual HRESULT STDMETHODCALLTYPE OnReleaseHardware(IWDFDevice* pDevice);

    virtual HRESULT STDMETHODCALLTYPE OnD0Entry(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual HRESULT STDMETHODCALLTYPE OnD0Exit(IWDFDevice*  pWdfDevice, WDF_POWER_DEVICE_STATE  previousState);
    virtual void STDMETHODCALLTYPE OnSurpriseRemoval(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryRemove(IWDFDevice*  pWdfDevice);
    virtual HRESULT STDMETHODCALLTYPE OnQueryStop(IWDFDevice*  pWdfDevice);

    virtual VOID STDMETHODCALLTYPE OnReaderCompletion(IWDFUsbTargetPipe* pPipe, IWDFMemory* pMemory, SIZE_T NumBytesTransferred, PVOID Context);
    virtual BOOL STDMETHODCALLTYPE OnReaderFailure(IWDFUsbTargetPipe * pPipe, HRESULT hrCompletion);

private:
    LONG m_cRefs;
    IWDFUsbTargetPipe* m_pFxUsbInterruptPipe;
    IWDFIoTargetStateManagement* m_pFxIoTargetInterruptPipeStateMgmt;

    HRESULT CreateUSBTargetDeviceObject (IWDFDevice* pFxDevice, IWDFUsbTargetDevice** ppUSBTargetDevice);
    HRESULT RetrieveUSBDeviceDescriptor (IWDFUsbTargetDevice* pUSBTargetDevice, PUSB_DEVICE_DESCRIPTOR DescriptorHeader, PULONG cbDescriptor);
    HRESULT ConfigureContinuousReader (IWDFUsbTargetPipe* pFxPipe);
};

Der folgende Beispielcode zeigt die QueryInterface-Implementierung des Geräterückrufobjekts.

HRESULT CDeviceCallback::QueryInterface(REFIID riid, LPVOID* ppvObject)
{
    if (ppvObject == NULL)
    {
        return E_INVALIDARG;
    }

    *ppvObject = NULL;

    HRESULT hr = E_NOINTERFACE;

    if(  IsEqualIID(riid, __uuidof(IPnpCallbackHardware))   ||  IsEqualIID(riid, __uuidof(IUnknown))  )
    {
        *ppvObject = static_cast<IPnpCallbackHardware*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;

    }

    if(  IsEqualIID(riid, __uuidof(IPnpCallback)))
    {
        *ppvObject = static_cast<IPnpCallback*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadComplete)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadComplete*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    if(  IsEqualIID(riid, __uuidof(IUsbTargetPipeContinuousReaderCallbackReadersFailed)))
    {
        *ppvObject = static_cast<IUsbTargetPipeContinuousReaderCallbackReadersFailed*>(this);
        reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
        hr = S_OK;
    }

    return hr;
}

Der folgende Beispielcode zeigt eine Implementierung eines Fehlerrückrufs. Wenn bei einer Leseanforderung ein Fehler auftritt, gibt die Methode den vom Framework gemeldeten Fehlercode im Debugger aus und weist das Framework an, die Pipe zurückzusetzen und dann den fortlaufenden Reader neu zu starten.

 BOOL CDeviceCallback::OnReaderFailure(
    IWDFUsbTargetPipe * pPipe,
    HRESULT hrCompletion
    )
{
    UNREFERENCED_PARAMETER(pPipe);
    UNREFERENCED_PARAMETER(hrCompletion);
    return TRUE;
}

Wenn der Clienttreiber keinen Fehlerrückruf bereitstellt und ein Fehler auftritt, setzt das Framework die USB-Pipe zurück und startet den fortlaufenden Reader neu.