Freigeben über


So wird's gemacht: Verwenden des kontinuierlichen Lesegeräts zum Lesen von Daten aus einem USB-Rohr

In diesem Thema wird das WDF-bereitgestellte Fortlaufendleserobjekt beschrieben. Die Verfahren in diesem Thema enthalten schrittweise Anleitungen zum Konfigurieren des Objekts und zum Lesen von Daten aus einem USB-Pipe.

Windows Driver Framework (WDF) stellt ein spezielles Objekt bereit, das als fortlaufender Reader bezeichnet wird. Dieses Objekt ermöglicht es einem USB-Clienttreiber, Daten aus Massen- und Unterbrechungsendpunkten kontinuierlich zu lesen, solange Daten verfügbar sind. Damit der Reader verwendet werden kann, muss der Clienttreiber über ein Handle für ein USB-Zielpipelineobjekt verfügen, das dem Endpunkt zugeordnet ist, von dem der Treiber Daten liest. Der Endpunkt muss in der aktiven Konfiguration enthalten sein. Sie können eine Konfiguration auf eine von 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 How to Select a Configuration for a USB Device and How to select an alternate setting in a USB interface.

Nach dem Erstellen des fortlaufenden Readers kann der Clienttreiber den Reader bei Bedarf starten und beenden. Der fortlaufende Reader, der sicherstellt, dass eine Leseanforderung immer für das Zielpipelineobjekt verfügbar ist, und der Clienttreiber ist immer bereit, 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 Energiezustand wechselt und den Reader neu startet, wenn das Gerät in den Arbeitszustand wechselt.

In diesem Artikel wird Folgendes verwendet:

Vor der Installation

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. In der Regel wird USBView im Ordner "Debuggers " 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 es im Gerätekontext.

    KMDF-Clienttreiber:

    Ein KMDF-Clienttreiber muss ein WDFUSBDEVICE-Handle abrufen, indem die WdfUsbTargetDeviceCreateWithParameters-Methode aufgerufen wird. Weitere Informationen finden Sie unter "Device source code" in Understanding the USB client driver code structure (KMDF).

    UMDF-Clienttreiber:

    Ein UMDF-Clienttreiber muss einen IWDFUsbTargetDevice-Zeiger durch Abfragen des Framework-Zielgeräteobjekts abrufen. Weitere Informationen finden Sie unter "IPnpCallbackHardware-Implementierung und USB-spezifische Aufgaben" unter Grundlegendes zur USB-Clienttreibercodestruktur (UMDF).For more information, see "IPnpCallbackHardware implementation and USB-specific tasks" in Understanding the USB client driver code structure (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 Standardeinstellung für alternative Einstellungen 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:

    Bei einem UMDF-Clienttreiber wählt das Framework die erste Konfiguration und die Standardeinstellung für jede Schnittstelle in dieser Konfiguration aus.

  • Der Clienttreiber muss über ein Handle für das Framework-Zielpipelineobjekt für den IN-Endpunkt verfügen. Weitere Informationen finden Sie unter Enumerieren 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 kontinuierlichen Readers in einem KMDF-Clienttreiber

  1. Initialisieren Sie eine WDF_USB_CONTINUOUS_READER_CONFIG Struktur, indem Sie das WDF_USB_CONTINUOUS_READER_CONFIG_INIT Makro 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 Zielpipelineobjekt 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 EvtDevicePrepareHardware-Rückruffunktion nach dem Aufzählen der Zielpipelineobjekte in der aktiven Einstellung.

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 Mitglieder 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 Abschlussroutine, die der Treiber implementiert. Das Framework ruft diese Routine auf, wenn sie eine Leseanforderung abgeschlossen hat. In der Abschlussroutine 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 von dem Gerät in einer einzigen Übertragung gelesen werden können. Der Clienttreiber kann diese Informationen in einer WDF_USB_PIPE_INFORMATION Struktur durch Aufrufen der WdfUsbInterfaceGetConfiguredPipe- oder WdfUsbTargetPipeGetInformation-Methode abrufen. Weitere Informationen finden Sie unter Enumerieren 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 festgelegt, um für viele Geräte mit vielen Prozessorkonfigurationen eine vernünftige Leistung zu bieten.

Zusätzlich zu den in WDF_USB_CONTINUOUS_READER_CONFIG_INIT angegebenen Konfigurationsparametern legt das Beispiel auch eine Fehlerroutine in WDF_USB_CONTINUOUS_READER_CONFIG fest. 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. Ziehen Sie beispielsweise einen Netzwerktreiber in Betracht, 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 zuerst die Größe des Pakets in seinem Aufruf an WDF_USB_CONTINUOUS_READER_CONFIG_INIT angeben. Anschließend muss der Treiber die Länge der Kopf- und Fußzeile angeben, indem HeaderLength- und TrailerLength-Member von WDF_USB_CONTINUOUS_READER_CONFIG festgelegt werden. Das Framework verwendet diese Werte, um die Byte-Offsets 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 Abschlussroutine

Das Framework ruft die vom Clienttreiber implementierte Abschlussroutine 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 Clienttreiber implementierte Abschlussroutine bei jedem Abschluss einer Anforderung auf. Das Framework weist für jeden Lesevorgang ein Speicherobjekt zu. In der Abschlussroutine ü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 nach jeder Abschlussroutine frei. Wenn der Clienttreiber die empfangenen Daten speichern möchte, muss der Treiber den Inhalt des Puffers in der Abschlussroutine kopieren.

Implementieren der Fehlerroutine

Das Framework ruft die vom Clienttreiber implementierte Fehlerroutine auf, um den Treiber darüber zu informieren, dass der fortlaufende Reader beim Verarbeiten einer Leseanforderung einen Fehler gemeldet hat. Das Framework übergibt den Zeiger an das Zielpipelineobjekt, 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 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 an, dass das Framework 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 Ständebedingung auf der Pipe auftritt. Beispielsweise kann der Treiber den USBD-Status überprüfen und eine Rücksetzpipeanforderung ausgeben, um den Zustand des Stalls zu löschen.

Informationen zur Fehlerwiederherstellung in Rohren finden Sie unter How to recover from USB pipe errors.

Starten und Beenden des fortlaufenden Readers

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

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

Im folgenden Beispielcode wird der fortlaufende Reader für das angegebene Zielpipelineobjekt 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 EvtDeviceD0Entry- und EvtDeviceD0Exit-Rückrufroutinen. Der Aktionsparameter von WdfIoTargetStop ermöglicht dem Clienttreiber, die Aktion für die ausstehenden Anforderungen in der Warteschlange zu 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 ausstehende 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 Readers in einem UMDF-Clienttreiber

Bevor Sie mit der Verwendung des fortlaufenden Readers beginnen, müssen Sie den Reader in Ihrer Implementierung von IPnpCallbackHardware::OnPrepareHardware-Methode konfigurieren. Führen Sie die folgenden Schritte aus, nachdem Sie einen Zeiger auf die IWDFUsbTargetPipe-Schnittstelle des Zielpipelineobjekts erhalten haben, das dem IN-Endpunkt zugeordnet ist:

Konfigurieren des fortlaufenden Readers in einem UMDF-Clienttreiber

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

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

  3. Call QueryInterface für das Geräterückrufobjekt und Abfrage für die IUsbTargetPipeContinuousReaderCallbackReadersFailed-Schnittstelle , 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 Methoden zum Abschluss und Fehlerrückruf.

    Die Methode konfiguriert den fortlaufenden Reader für das Zielpipelineobjekt. 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 Zielpipelineobjekt konfiguriert. Im Beispiel wird davon ausgegangen, dass das vom Aufrufer angegebene Zielpipelineobjekt einem IN-Endpunkt zugeordnet ist. Der fortlaufende Reader ist so konfiguriert, dass USBD_DEFAULT_MAXIMUM_TRANSFER_SIZE Bytes gelesen werden. die Standardanzahl der ausstehenden Anforderungen mithilfe des Frameworks zu verwenden; um die vom Clienttreiber bereitgestellten Abschluss- 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 Status des Zielpipelineobjekts an, wenn das Gerät in den Arbeitszustand (D0) wechselt und ihn verlässt.

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

Der fortlaufende Reader verwendet keine powerverwalteten Warteschlangen zum Senden von Anforderungen. Daher müssen Sie das Zielpipelineobjekt explizit starten oder beenden, wenn sich der Energiezustand des Geräts ändert. Zum Ändern des Zustands des Zielpipelineobjekts können Sie die vom Framework implementierte IWDFIoTargetStateManagement-Schnittstelle verwenden. Nachdem Sie einen Zeiger auf die IWDFUsbTargetPipe-Schnittstelle des Dem IN-Endpunkt zugeordneten Zielpipeobjekts erhalten haben, führen Sie die folgenden Schritte aus:

Implementieren der Zustandsverwaltung

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

  2. Implementieren Sie die IPnpCallback-Schnittstelle im Geräterückrufobjekt.

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

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

Nachdem das Gerät in einen Arbeitszustand (D0) wechselt, ruft das Framework die vom Clienttreiber bereitgestellte D0-Entry-Rückrufmethode auf, die das Zielpipelineobjekt startet. Wenn das Gerät den D0-Zustand verlässt, ruft das Framework die D0-Exit-Rückrufmethode auf. Das Zielpipelineobjekt schließt die Anzahl der ausstehenden Leseanforderungen ab, die vom Clienttreiber konfiguriert wurden, und beendet die Annahme neuer Anforderungen. Im folgenden Beispielcode wird die IPnpCallback-Schnittstelle im Geräterückrufobjekt implementiert.

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

Im folgenden Beispielcode wird gezeigt, wie Sie einen Zeiger auf die IWDFIoTargetStateManagement-Schnittstelle des Zielpipelineobjekts 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 bereitstellen, 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 Referenzanzahl des Rückrufobjekts erhöht und dann den IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstellenzeiger zurückgibt.

  3. Greifen Sie in der Implementierung der IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion-Methode auf die von der Pipe gelesenen Daten zu. Der pMemory-Parameter verweist auf den vom Framework zugewiesenen Speicher, der die Daten enthält. Sie können IWDFMemory::GetDataBuffer aufrufen, um den Puffer abzurufen, der die Daten enthält. Der Puffer enthält jedoch die Kopfzeile, die durch den NumBytesTransferred-Parameter von OnReaderCompletion angegebene Datenlänge enthält jedoch nicht die Headerlänge. Die Headerlänge wird vom Clienttreiber angegeben, während der fortlaufende Reader im Aufruf des Treibers an IWDFUsbTargetPipe2::ConfigureContinuousReader konfiguriert wird.

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

Jedes Mal, wenn die Daten auf dem Gerät auf dem Endpunkt verfügbar sind, schließt das Zielpipelineobjekt 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 Zielpipelineobjekt einen Fehler in der Leseanforderung meldet.

Der folgende Beispielcode implementiert die IUsbTargetPipeContinuousReaderCallbackReadComplete-Schnittstelle im 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 Daten aus dem von IUsbTargetPipeContinuousReaderCallbackReadComplete::OnReaderCompletion zurückgegebenen Puffer abgerufen werden. Jedes Mal, wenn das Zielpipelineobjekt eine Leseanforderung erfolgreich abgeschlossen hat, ruft das Framework OnReaderCompletion auf. Das Beispiel ruft den Puffer ab, der Daten enthält, und druckt den Inhalt in der Debuggerausgabe.

 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 abrufen, wenn beim Abschließen einer Leseanforderung ein Fehler im Zielpipelineobjekt auftritt. 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. Das folgende Verfahren beschreibt, wie der Fehlerrückruf implementiert wird.

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. Geben Sie bei der Implementierung der IUsbTargetPipeContinuousReaderCallbackReadersFailed::OnReaderFailure-Methode die Fehlerbehandlung der fehlgeschlagenen Leseanforderung an.

    Wenn der fortlaufende Reader eine Leseanforderung nicht abschließen kann 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 bestimmte Fehlerbehandlungen bereitstellen. Wenn das Framework beispielsweise die Pipe zurücksetzen und dann den fortlaufenden Reader neu starten soll, stellen Sie sicher, dass der Rückruf TRUE zurückgibt.

    Hinweis : Rufen Sie 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 auf dem 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 eine Leseanforderung fehlschlägt, druckt die Methode den vom Framework im Debugger gemeldeten Fehlercode 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.