Share via


Grundlegendes zur Codestruktur des USB-Clienttreibers (UMDF)

In diesem Thema erfahren Sie mehr über den Quellcode für einen UMDF-basierten USB-Clienttreiber. Die Codebeispiele werden von der in Microsoft Visual Studio enthaltenen USB-Benutzermodustreibervorlage generiert. Der Vorlagencode verwendet die Active Template Library (ATL), um die COM-Infrastruktur zu generieren. ATL und Details zur COM-Implementierung im Clienttreiber werden hier nicht erläutert.

Anweisungen zum Generieren des UMDF-Vorlagencodes finden Sie unter Schreiben Ihres ersten USB-Clienttreibers (UMDF). Der Vorlagencode wird in den folgenden Abschnitten erläutert:

Bevor wir die Details des Vorlagencodes besprechen, sehen wir uns einige Deklarationen in der Headerdatei (Internal.h) an, die für die UMDF-Treiberentwicklung relevant sind.

Internal.h enthält die folgenden Dateien, die im Windows Driver Kit (WDK) enthalten sind:

#include "atlbase.h"
#include "atlcom.h"

#include "wudfddi.h"
#include "wudfusb.h"

Atlbase.h und atlcom.h enthalten Deklarationen für die ATL-Unterstützung. Jede klasse, die vom Clienttreiber implementiert wird, implementiert die öffentliche ATL-Klasse CComObjectRootEx.

Wudfddi.h ist immer für die UMDF-Treiberentwicklung enthalten. Die Headerdatei enthält verschiedene Deklarationen und Definitionen von Methoden und Strukturen, die Sie zum Kompilieren eines UMDF-Treibers benötigen.

Wudfusb.h enthält Deklarationen und Definitionen von UMDF-Strukturen und -Methoden, die für die Kommunikation mit den vom Framework bereitgestellten USB-E/A-Zielobjekten erforderlich sind.

Der nächste Block in Internal.h deklariert eine GUID-Konstante für die Geräteschnittstelle. Anwendungen können diese GUID verwenden, um mithilfe von SetupDiXxx-APIs ein Handle für das Gerät zu öffnen. Die GUID wird registriert, nachdem das Framework das Geräteobjekt erstellt hat.

// Device Interface GUID
// f74570e5-ed0c-4230-a7a5-a56264465548

DEFINE_GUID(GUID_DEVINTERFACE_MyUSBDriver_UMDF_,
    0xf74570e5,0xed0c,0x4230,0xa7,0xa5,0xa5,0x62,0x64,0x46,0x55,0x48);

Im nächsten Teil werden das Ablaufverfolgungsmakro und die Ablaufverfolgungs-GUID deklariert. Notieren Sie sich die Ablaufverfolgungs-GUID. Sie benötigen sie, um die Ablaufverfolgung zu aktivieren.

#define WPP_CONTROL_GUIDS                                              \
    WPP_DEFINE_CONTROL_GUID(                                           \
        MyDriver1TraceGuid, (f0261b19,c295,4a92,aa8e,c6316c82cdf0),    \
                                                                       \
        WPP_DEFINE_BIT(MYDRIVER_ALL_INFO)                              \
        WPP_DEFINE_BIT(TRACE_DRIVER)                                   \
        WPP_DEFINE_BIT(TRACE_DEVICE)                                   \
        WPP_DEFINE_BIT(TRACE_QUEUE)                                    \
        )                             

#define WPP_FLAG_LEVEL_LOGGER(flag, level)                             \
    WPP_LEVEL_LOGGER(flag)

#define WPP_FLAG_LEVEL_ENABLED(flag, level)                            \
    (WPP_LEVEL_ENABLED(flag) &&                                        \
     WPP_CONTROL(WPP_BIT_ ## flag).Level >= level)

#define WPP_LEVEL_FLAGS_LOGGER(lvl,flags) \
           WPP_LEVEL_LOGGER(flags)

#define WPP_LEVEL_FLAGS_ENABLED(lvl, flags) \
           (WPP_LEVEL_ENABLED(flags) && WPP_CONTROL(WPP_BIT_ ## flags).Level >= lvl)

Die nächste Zeile in Internal.h forward deklariert die vom Clienttreiber implementierte Klasse für das Warteschlangenrückrufobjekt. Es enthält auch andere Projektdateien, die von der Vorlage generiert wurden. Die Implementierungs- und Projektheaderdateien werden weiter unten in diesem Thema erläutert.

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

#include "Driver.h"
#include "Device.h"
#include "IoQueue.h"

Nachdem der Clienttreiber installiert wurde, lädt Windows den Clienttreiber und das Framework in einer instance des Hostprozesses. Von hier aus lädt und initialisiert das Framework den Clienttreiber. Das Framework führt die folgenden Aufgaben aus:

  1. Erstellt ein Treiberobjekt im Framework, das Ihren Clienttreiber darstellt.
  2. Fordert einen IDriverEntry-Schnittstellenzeiger aus der Klassenfactory an.
  3. Erstellt ein Geräteobjekt im Framework.
  4. Initialisiert das Geräteobjekt, nachdem der PnP-Manager das Gerät gestartet hat.

Während der Treiber geladen und initialisiert wird, treten mehrere Ereignisse auf, und das Framework ermöglicht es dem Clienttreiber, diese zu verarbeiten. Auf der Seite des Clienttreibers führt der Treiber die folgenden Aufgaben aus:

  1. Implementiert und exportiert die DllGetClassObject-Funktion aus Ihrem Clienttreibermodul, sodass das Framework einen Verweis auf den Treiber abrufen kann.
  2. Stellt eine Rückrufklasse bereit, die die IDriverEntry-Schnittstelle implementiert.
  3. Stellt eine Rückrufklasse bereit, die IPnpCallbackXxx-Schnittstellen implementiert.
  4. Ruft einen Verweis auf das Geräteobjekt ab und konfiguriert es gemäß den Anforderungen des Clienttreibers.

Quellcode des Treiberrückrufs

Das Framework erstellt das Treiberobjekt, das die instance des von Windows geladenen Clienttreibers darstellt. Der Clienttreiber stellt mindestens einen Treiberrückruf bereit, der den Treiber beim Framework registriert.

Der vollständige Quellcode für den Treiberrückruf befindet sich in Driver.h und Driver.c.

Der Clienttreiber muss eine Treiberrückrufklasse definieren, die IUnknown - und IDriverEntry-Schnittstellen implementiert. Die Headerdatei Driver.h deklariert eine Klasse namens CMyDriver, die den Treiberrückruf definiert.

EXTERN_C const CLSID CLSID_Driver;

class CMyDriver :
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CMyDriver, &CLSID_Driver>,
    public IDriverEntry
{
public:

    CMyDriver()
    {
    }

    DECLARE_NO_REGISTRY()

    DECLARE_NOT_AGGREGATABLE(CMyDriver)

    BEGIN_COM_MAP(CMyDriver)
        COM_INTERFACE_ENTRY(IDriverEntry)
    END_COM_MAP()

public:

    // IDriverEntry methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnInitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return S_OK;
    }

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnDeviceAdd(
        __in IWDFDriver *FxWdfDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeinitialize(
        __in IWDFDriver *FxWdfDriver
        )
    {
        UNREFERENCED_PARAMETER(FxWdfDriver);
        return;
    }

};

OBJECT_ENTRY_AUTO(CLSID_Driver, CMyDriver)

Der Treiberrückruf muss eine COM-Klasse sein, d. h. er muss IUnknown und die zugehörigen Methoden implementieren. Im Vorlagencode enthalten die ATL-Klassen CComObjectRootEx und CComCoClass die IUnknown-Methoden .

Nachdem Windows den Hostprozess instanziiert hat, erstellt das Framework das Treiberobjekt. Hierzu erstellt das Framework eine instance der Treiberrückrufklasse und ruft die Treiberimplementierung von DllGetClassObject auf (im Abschnitt Treibereintragsquellcode erläutert), und um den IDriverEntry-Schnittstellenzeiger des Clienttreibers abzurufen. Dieser Aufruf registriert das Treiberrückrufobjekt beim Frameworktreiberobjekt. Nach erfolgreicher Registrierung ruft das Framework die Implementierung des Clienttreibers auf, wenn bestimmte treiberspezifische Ereignisse auftreten. Die erste Methode, die das Framework aufruft, ist die IDriverEntry::OnInitialize-Methode . In der Implementierung von IDriverEntry::OnInitialize durch den Clienttreiber kann der Clienttreiber globale Treiberressourcen zuordnen. Diese Ressourcen müssen in IDriverEntry::OnDeinitialize freigegeben werden, das vom Framework aufgerufen wird, bevor es sich auf das Entladen des Clienttreibers vorbereitet. Der Vorlagencode bietet eine minimale Implementierung für die Methoden OnInitialize und OnDeinitialize .

Die wichtigste Methode von IDriverEntry ist IDriverEntry::OnDeviceAdd. Bevor das Framework das Framework-Geräteobjekt erstellt (im nächsten Abschnitt erläutert), ruft es die IDriverEntry::OnDeviceAdd-Implementierung des Treibers auf. Beim Aufrufen der -Methode übergibt das Framework einen IWDFDriver-Zeiger an das Treiberobjekt und einen IWDFDeviceInitialize-Zeiger . Der Clienttreiber kann IWDFDeviceInitialize-Methoden aufrufen, um bestimmte Konfigurationsoptionen anzugeben.

In der Regel führt der Clienttreiber die folgenden Aufgaben in seiner IDriverEntry::OnDeviceAdd-Implementierung aus:

  • Gibt Konfigurationsinformationen für das zu erstellende Geräteobjekt an.
  • Instanziiert die Geräterückrufklasse des Treibers.
  • Erstellt das Framework-Geräteobjekt und registriert sein Geräterückrufobjekt beim Framework.
  • Initialisiert das Framework-Geräteobjekt.
  • Registriert die Geräteschnittstellen-GUID des Clienttreibers.

Im Vorlagencode ruft IDriverEntry::OnDeviceAdd die statische Methode CMyDevice::CreateInstanceAndInitialize auf, die in der Geräterückrufklasse definiert ist. Die statische Methode instanziiert zuerst die Geräterückrufklasse des Clienttreibers und erstellt dann das Framework-Geräteobjekt. Die Geräterückrufklasse definiert auch eine öffentliche Methode namens Configure, die verbleibende Aufgaben ausführt, die in der vorherigen Liste erwähnt werden. Die Implementierung der Geräterückrufklasse wird im nächsten Abschnitt erläutert. Das folgende Codebeispiel zeigt die IDriverEntry::OnDeviceAdd-Implementierung im Vorlagencode.

HRESULT
CMyDriver::OnDeviceAdd(
    __in IWDFDriver *FxWdfDriver,
    __in IWDFDeviceInitialize *FxDeviceInit
    )
{
    HRESULT hr = S_OK;
    CMyDevice *device = NULL;

    hr = CMyDevice::CreateInstanceAndInitialize(FxWdfDriver,
                                                FxDeviceInit,
                                                &device);

    if (SUCCEEDED(hr))
    {
        hr = device->Configure();
    }

    return hr;
}

Das folgende Codebeispiel zeigt die Geräteklassendeklaration in Device.h.

class CMyDevice :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IPnpCallbackHardware
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyDevice)

    BEGIN_COM_MAP(CMyDevice)
        COM_INTERFACE_ENTRY(IPnpCallbackHardware)
    END_COM_MAP()

    CMyDevice() :
        m_FxDevice(NULL),
        m_IoQueue(NULL),
        m_FxUsbDevice(NULL)
    {
    }

    ~CMyDevice()
    {
    }

private:

    IWDFDevice *            m_FxDevice;

    CMyIoQueue *            m_IoQueue;

    IWDFUsbTargetDevice *   m_FxUsbDevice;

private:

    HRESULT
    Initialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit
        );

public:

    static
    HRESULT
    CreateInstanceAndInitialize(
        __in IWDFDriver *FxDriver,
        __in IWDFDeviceInitialize *FxDeviceInit,
        __out CMyDevice **Device
        );

    HRESULT
    Configure(
        VOID
        );
public:

    // IPnpCallbackHardware methods

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnPrepareHardware(
            __in IWDFDevice *FxDevice
            );

    virtual
    HRESULT
    STDMETHODCALLTYPE
    OnReleaseHardware(
        __in IWDFDevice *FxDevice
        );

};

Quellcode des Geräterückrufs

Das Framework-Geräteobjekt ist ein instance der Frameworkklasse, das das Geräteobjekt darstellt, das in den Gerätestapel des Clienttreibers geladen wird. Informationen zur Funktionalität eines Geräteobjekts finden Sie unter Geräteknoten und Gerätestapel.

Der vollständige Quellcode für das Geräteobjekt befindet sich unter Device.h und Device.c.

Die Framework-Geräteklasse implementiert die IWDFDevice-Schnittstelle . Der Clienttreiber ist für das Erstellen eines instance dieser Klasse in der Implementierung von IDriverEntry::OnDeviceAdd durch den Treiber verantwortlich. Nachdem das Objekt erstellt wurde, ruft der Clienttreiber einen IWDFDevice-Zeiger auf das neue Objekt ab und ruft Methoden für diese Schnittstelle auf, um die Vorgänge des Geräteobjekts zu verwalten.

IDriverEntry::OnDeviceAdd-Implementierung

Im vorherigen Abschnitt haben Sie kurz die Aufgaben gesehen, die ein Clienttreiber in IDriverEntry::OnDeviceAdd ausführt. Hier finden Sie weitere Informationen zu diesen Aufgaben. Der Clienttreiber:

  • Gibt Konfigurationsinformationen für das zu erstellende Geräteobjekt an.

    Im Frameworkaufruf der Implementierung der IDriverEntry::OnDeviceAdd-Methode durch den Clienttreiber übergibt das Framework einen IWDFDeviceInitialize-Zeiger . Der Clienttreiber verwendet diesen Zeiger, um Konfigurationsinformationen für das zu erstellende Geräteobjekt anzugeben. Der Clienttreiber gibt beispielsweise an, ob der Clienttreiber ein Filter oder ein Funktionstreiber ist. Um den Clienttreiber als Filtertreiber zu identifizieren, wird IWDFDeviceInitialize::SetFilter aufgerufen. In diesem Fall erstellt das Framework ein Filtergeräteobjekt (FiDO). Andernfalls wird ein Funktionsgeräteobjekt (Function Device Object, FDO) erstellt. Eine weitere Option, die Sie festlegen können, ist der Synchronisierungsmodus, indem Sie IWDFDeviceInitialize::SetLockingConstraint aufrufen.

  • Ruft die IWDFDriver::CreateDevice-Methode auf, indem sie den IWDFDeviceInitialize-Schnittstellenzeiger , einen IUnknown-Verweis des Geräterückrufobjekts und eine IWDFDevice-Variable mit Zeiger übergeben.

    Wenn der IWDFDriver::CreateDevice-Aufruf erfolgreich ist:

    • Das Framework erstellt das Geräteobjekt.

    • Das Framework registriert den Geräterückruf beim Framework.

      Nachdem der Geräterückruf mit dem Framework-Geräteobjekt gekoppelt wurde, behandeln das Framework und der Clienttreiber bestimmte Ereignisse, z. B. Änderungen des PnP-Zustands und des Energiezustands. Wenn beispielsweise der PnP-Manager das Gerät startet, wird das Framework benachrichtigt. Das Framework ruft dann die IPnpCallbackHardware::OnPrepareHardware-Implementierung des Geräterückrufs auf. Jeder Clienttreiber muss mindestens ein Geräterückrufobjekt registrieren.

    • Der Clienttreiber empfängt die Adresse des neuen Geräteobjekts in der Variablen IWDFDevice . Nach dem Empfang eines Zeigers auf das Framework-Geräteobjekt kann der Clienttreiber mit Initialisierungstasks fortfahren, z. B. das Einrichten von Warteschlangen für den E/A-Fluss und das Registrieren der Geräteschnittstellen-GUID.

  • Ruft IWDFDevice::CreateDeviceInterface auf, um die Geräteschnittstellen-GUID des Clienttreibers zu registrieren. Die Anwendungen können die GUID verwenden, um Anforderungen an den Clienttreiber zu senden. Die GUID-Konstante wird in Internal.h deklariert.

  • Initialisiert Warteschlangen für E/A-Übertragungen an und vom Gerät.

Der Vorlagencode definiert die Hilfsmethode Initialize, die Konfigurationsinformationen angibt und das Geräteobjekt erstellt.

Das folgende Codebeispiel zeigt Implementierungen für Initialize.

HRESULT
CMyDevice::Initialize(
    __in IWDFDriver           * FxDriver,
    __in IWDFDeviceInitialize * FxDeviceInit
    )
{
    IWDFDevice *fxDevice = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    FxDeviceInit->SetLockingConstraint(None);

    FxDeviceInit->SetPowerPolicyOwnership(TRUE);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get IUnknown %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDriver->CreateDevice(FxDeviceInit, unknown, &fxDevice);
    DriverSafeRelease(unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create a framework device %!hresult!",
                    hr);
        goto Exit;
    }

     m_FxDevice = fxDevice;

     DriverSafeRelease(fxDevice);

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

Im vorherigen Codebeispiel erstellt der Clienttreiber das Geräteobjekt und registriert seinen Geräterückruf. Vor dem Erstellen des Geräteobjekts gibt der Treiber seine Konfigurationspräferenz an, indem er Methoden für den IWDFDeviceInitialize-Schnittstellenzeiger aufruft . Dies ist derselbe Zeiger, der vom Framework im vorherigen Aufruf der IDriverEntry::OnDeviceAdd-Methode des Clienttreibers übergeben wurde.

Der Clienttreiber gibt an, dass er der Besitzer der Energierichtlinie für das Geräteobjekt ist. Als Besitzer der Energierichtlinie bestimmt der Clienttreiber den entsprechenden Energiezustand, den das Gerät bei einer Änderung des Systemenergiezustands eingeben soll. Der Treiber ist auch für das Senden relevanter Anforderungen an das Gerät verantwortlich, um den Energiezustandsübergang zu ermöglichen. Standardmäßig ist ein UMDF-basierter Clienttreiber nicht der Besitzer der Energierichtlinie. das Framework verarbeitet alle Energiezustandsübergänge. Das Framework sendet das Gerät automatisch an D3 , wenn das System in einen Ruhezustand wechselt, und versetzt das Gerät umgekehrt wieder auf D0 , wenn das System in den Arbeitszustand von S0 wechselt. Weitere Informationen finden Sie unter Power Policy Ownership in UMDF.

Eine weitere Konfigurationsoption besteht darin, anzugeben, ob der Clienttreiber der Filtertreiber oder der Funktionstreiber für das Gerät ist. Beachten Sie, dass der Clienttreiber im Codebeispiel seine Einstellung nicht explizit angibt. Das bedeutet, dass der Clienttreiber der Funktionstreiber ist und das Framework eine FDO im Gerätestapel erstellen sollte. Wenn der Clienttreiber der Filtertreiber sein soll, muss der Treiber die IWDFDeviceInitialize::SetFilter-Methode aufrufen. In diesem Fall erstellt das Framework eine FiDO im Gerätestapel.

Der Clienttreiber gibt auch an, dass keiner der Aufrufe des Frameworks an die Rückrufe des Clienttreibers synchronisiert wird. Der Clienttreiber verarbeitet alle Synchronisierungsaufgaben. Um diese Einstellung anzugeben, ruft der Clienttreiber die IWDFDeviceInitialize::SetLockingConstraint-Methode auf.

Als Nächstes ruft der Clienttreiber einen IUnknown-Zeiger auf seine Geräterückrufklasse ab, indem er IUnknown::QueryInterface aufruft. Anschließend ruft der Clienttreiber IWDFDriver::CreateDevice auf, wodurch das Framework-Geräteobjekt erstellt und der Geräterückruf des Clienttreibers mithilfe des IUnknown-Zeigers registriert wird.

Beachten Sie, dass der Clienttreiber die Adresse des Geräteobjekts (empfangen über den IWDFDriver::CreateDevice-Aufruf ) in einem privaten Datenmember der Geräterückrufklasse speichert und diesen Verweis dann durch Aufrufen von DriverSafeRelease (in Internal.h definierte Inlinefunktion) freigibt. Dies liegt daran, dass die Lebensdauer des Geräteobjekts vom Framework nachverfolgt wird. Daher muss der Clienttreiber keine zusätzliche Verweisanzahl des Geräteobjekts beibehalten.

Der Vorlagencode definiert die öffentliche Methode Configure, die die Geräteschnittstellen-GUID registriert und Warteschlangen einrichtet. Das folgende Codebeispiel zeigt die Definition der Configure-Methode in der Geräterückrufklasse CMyDevice. Configure wird von IDriverEntry::OnDeviceAdd aufgerufen, nachdem das Framework-Geräteobjekt erstellt wurde.

CMyDevice::Configure(
    VOID
    )
{

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

     hr = CMyIoQueue::CreateInstanceAndInitialize(m_FxDevice, this, &m_IoQueue);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create and initialize queue %!hresult!",
                    hr);
        goto Exit;
    }

    hr = m_IoQueue->Configure();
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to configure queue %!hresult!",
                    hr);
        goto Exit;
    } 

    hr = m_FxDevice->CreateDeviceInterface(&GUID_DEVINTERFACE_MyUSBDriver_UMDF_,NULL);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create device interface %!hresult!",
                    hr);
        goto Exit;
    }

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

Im vorherigen Codebeispiel führt der Clienttreiber zwei Standard Aufgaben aus: Initialisieren von Warteschlangen für den E/A-Fluss und Registrieren der Geräteschnittstellen-GUID.

Die Warteschlangen werden in der CMyIoQueue-Klasse erstellt und konfiguriert. Die erste Aufgabe besteht darin, diese Klasse durch Aufrufen der statischen Methode createInstanceAndInitialize zu instanziieren. Der Clienttreiber ruft Configure auf, um Warteschlangen zu initialisieren. CreateInstanceAndInitialize und Configure werden in CMyIoQueue deklariert, was weiter unten in diesem Thema erläutert wird.

Der Clienttreiber ruft auch IWDFDevice::CreateDeviceInterface auf, um die GUID der Geräteschnittstelle des Clienttreibers zu registrieren. Die Anwendungen können die GUID verwenden, um Anforderungen an den Clienttreiber zu senden. Die GUID-Konstante wird in Internal.h deklariert.

IPnpCallbackHardware-Implementierung und USB-spezifische Aufgaben

Als Nächstes sehen wir uns die Implementierung der IPnpCallbackHardware-Schnittstelle in Device.cpp an.

Jede Geräterückrufklasse muss die IPnpCallbackHardware-Schnittstelle implementieren. Diese Schnittstelle verfügt über zwei Methoden: IPnpCallbackHardware::OnPrepareHardware und IPnpCallbackHardware::OnReleaseHardware. Das Framework ruft diese Methoden als Reaktion auf zwei Ereignisse auf: wenn der PnP-Manager das Gerät startet und das Gerät entfernt. Wenn ein Gerät gestartet wird, wird die Kommunikation mit der Hardware hergestellt, aber das Gerät wurde nicht in den Betriebszustand (D0) versetzt. Daher kann der Clienttreiber in IPnpCallbackHardware::OnPrepareHardware Geräteinformationen von der Hardware abrufen, Ressourcen zuordnen und Frameworkobjekte initialisieren, die während der Lebensdauer des Treibers erforderlich sind. Wenn der PnP-Manager das Gerät entfernt, wird der Treiber aus dem System entladen. Das Framework ruft die IPnpCallbackHardware::OnReleaseHardware-Implementierung des Clienttreibers auf, in der der Treiber diese Ressourcen und Frameworkobjekte freigeben kann.

Der PnP-Manager kann andere Arten von Ereignissen generieren, die sich aus PnP-Zustandsänderungen ergeben. Das Framework bietet eine Standardbehandlung für diese Ereignisse. Der Clienttreiber kann an der Behandlung dieser Ereignisse teilnehmen. Stellen Sie sich ein Szenario vor, in dem das USB-Gerät vom Host getrennt ist. Der PnP-Manager erkennt dieses Ereignis und benachrichtigt das Framework. Wenn der Clienttreiber als Reaktion auf das Ereignis zusätzliche Aufgaben ausführen möchte, muss der Treiber die IPnpCallback-Schnittstelle und die zugehörige IPnpCallback::OnSurpriseRemoval-Methode in der Geräterückrufklasse implementieren. Andernfalls fährt das Framework mit der Standardbehandlung des Ereignisses fort.

Ein USB-Clienttreiber muss Informationen zu den unterstützten Schnittstellen, alternativen Einstellungen und Endpunkten abrufen und konfigurieren, bevor E/A-Anforderungen für die Datenübertragung gesendet werden. UMDF bietet spezielle E/A-Zielobjekte, die viele konfigurationsaufgaben für den Clienttreiber vereinfachen. Zum Konfigurieren eines USB-Geräts benötigt der Clienttreiber Geräteinformationen, die erst verfügbar sind, nachdem der PnP-Manager das Gerät gestartet hat.

Dieser Vorlagencode erstellt diese Objekte in der IPnpCallbackHardware::OnPrepareHardware-Methode .

In der Regel führt der Clienttreiber eine oder mehrere dieser Konfigurationsaufgaben aus (abhängig vom Entwurf des Geräts):

  1. Ruft Informationen zur aktuellen Konfiguration ab, z. B. die Anzahl der Schnittstellen. Das Framework wählt die erste Konfiguration auf einem USB-Gerät aus. Der Clienttreiber kann bei Geräten mit mehreren Konfigurationen keine andere Konfiguration auswählen.
  2. Ruft Informationen zu Schnittstellen ab, z. B. die Anzahl der Endpunkte.
  3. Ändert die alternative Einstellung innerhalb jeder Schnittstelle, wenn die Schnittstelle mehrere Einstellungen unterstützt. Standardmäßig wählt das Framework die erste alternative Einstellung jeder Schnittstelle in der ersten Konfiguration auf einem USB-Gerät aus. Der Clienttreiber kann eine alternative Einstellung auswählen.
  4. Ruft Informationen zu Endpunkten innerhalb jeder Schnittstelle ab.

Um diese Aufgaben auszuführen, kann der Clienttreiber diese Typen von spezialisierten USB-E/A-Zielobjekten verwenden, die von der WDF bereitgestellt werden.

USB-E/A-Zielobjekt BESCHREIBUNG UMDF-Schnittstelle
Zielgerätobjekt Stellt ein USB-Gerät dar und stellt Methoden zum Abrufen des Gerätedeskriptors und zum Senden von Steuerungsanforderungen an das Gerät bereit. IWDFUsbTargetDevice
Zielschnittstellenobjekt Stellt eine einzelne Schnittstelle dar und stellt Methoden bereit, die ein Clienttreiber aufrufen kann, um eine alternative Einstellung auszuwählen und Informationen über die Einstellung abzurufen. IWDFUsbInterface
Zielpipeobjekt Stellt eine einzelne Pipe für einen Endpunkt dar, der in der aktuellen alternativen Einstellung für eine Schnittstelle konfiguriert ist. Der USB-Bustreiber wählt jede Schnittstelle in der ausgewählten Konfiguration aus und richtet einen Kommunikationskanal zu jedem Endpunkt innerhalb der Schnittstelle ein. In der USB-Terminologie wird dieser Kommunikationskanal als Pipe bezeichnet. IWDFUsbTargetPipe

Das folgende Codebeispiel zeigt die Implementierung für IPnpCallbackHardware::OnPrepareHardware.

HRESULT
CMyDevice::OnPrepareHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    HRESULT hr;
    IWDFUsbTargetFactory *usbFactory = NULL;
    IWDFUsbTargetDevice *usbDevice = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    hr = m_FxDevice->QueryInterface(IID_PPV_ARGS(&usbFactory));

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to get USB target factory %!hresult!",
                    hr);
        goto Exit;
    }

    hr = usbFactory->CreateUsbTargetDevice(&usbDevice);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_DEVICE,
                    "%!FUNC! Failed to create USB target device %!hresult!",
                    hr);

        goto Exit;
    }

     m_FxUsbDevice = usbDevice;

Exit:

    DriverSafeRelease(usbDevice);

    DriverSafeRelease(usbFactory);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return hr;
}

Um die USB-E/A-Zielobjekte des Frameworks zu verwenden, muss der Clienttreiber zuerst das USB-Zielgerätobjekt erstellen. Im Frameworkobjektmodell ist das USB-Zielgerätobjekt ein untergeordnetes Element des Geräteobjekts, das ein USB-Gerät darstellt. Das USB-Zielgerätobjekt wird vom Framework implementiert und führt alle Aufgaben auf Geräteebene eines USB-Geräts aus, z. B. das Auswählen einer Konfiguration.

Im vorherigen Codebeispiel fragt der Clienttreiber das Framework-Geräteobjekt ab und ruft einen IWDFUsbTargetFactory-Zeiger auf die Klassenfactory ab, die das USB-Zielgerätobjekt erstellt. Mithilfe dieses Zeigers ruft der Clienttreiber die IWDFUsbTargetDevice::CreateUsbTargetDevice-Methode auf. Die -Methode erstellt das USB-Zielgerätobjekt und gibt einen Zeiger auf die IWDFUsbTargetDevice-Schnittstelle zurück. Die -Methode wählt auch die Standardkonfiguration (erste) und die alternative Einstellung 0 für jede Schnittstelle in dieser Konfiguration aus.

Der Vorlagencode speichert die Adresse des USB-Zielgeräteobjekts (empfangen über den IWDFDriver::CreateDevice-Aufruf ) in einem privaten Datenmember der Geräterückrufklasse und gibt diesen Verweis dann durch Aufrufen von DriverSafeRelease frei. Die Referenzanzahl des USB-Zielgerätobjekts wird vom Framework verwaltet. Das Objekt ist aktiv, solange das Geräteobjekt aktiv ist. Der Clienttreiber muss den Verweis in IPnpCallbackHardware::OnReleaseHardware freigeben.

Nachdem der Clienttreiber das USB-Zielgerätobjekt erstellt hat, ruft der Treiber IWDFUsbTargetDevice-Methoden auf, um die folgenden Aufgaben auszuführen:

  • Rufen Sie das Gerät, die Konfiguration, die Schnittstellendeskriptoren und andere Informationen wie die Gerätegeschwindigkeit ab.
  • Formatieren und Senden von E/A-Steuerelementanforderungen an den Standardendpunkt.
  • Legen Sie die Energierichtlinie für das gesamte USB-Gerät fest.

Weitere Informationen finden Sie unter Arbeiten mit USB-Geräten in UMDF. Das folgende Codebeispiel zeigt die Implementierung für IPnpCallbackHardware::OnReleaseHardware.

HRESULT
CMyDevice::OnReleaseHardware(
    __in IWDFDevice * /* FxDevice */
    )
{
    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Entry");

    if (m_FxUsbDevice != NULL) {

        m_FxUsbDevice->DeleteWdfObject();
        m_FxUsbDevice = NULL;
    }

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_DEVICE, "%!FUNC! Exit");

    return S_OK;
}

Warteschlangenquellcode

Das Framework-Warteschlangenobjekt stellt die E/A-Warteschlange für ein bestimmtes Framework-Geräteobjekt dar. Der vollständige Quellcode für das Warteschlangenobjekt befindet sich in IoQueue.h und IoQueue.c.

IoQueue.h

Die Headerdatei IoQueue.h deklariert die Warteschlangenrückrufklasse.

class CMyIoQueue :
    public CComObjectRootEx<CComMultiThreadModel>,
    public IQueueCallbackDeviceIoControl
{

public:

    DECLARE_NOT_AGGREGATABLE(CMyIoQueue)

    BEGIN_COM_MAP(CMyIoQueue)
        COM_INTERFACE_ENTRY(IQueueCallbackDeviceIoControl)
    END_COM_MAP()

    CMyIoQueue() : 
        m_FxQueue(NULL),
        m_Device(NULL)
    {
    }

    ~CMyIoQueue()
    {
        // empty
    }

    HRESULT
    Initialize(
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice
        );

    static 
    HRESULT 
    CreateInstanceAndInitialize( 
        __in IWDFDevice *FxDevice,
        __in CMyDevice *MyDevice,
        __out CMyIoQueue**    Queue
        );

    HRESULT
    Configure(
        VOID
        )
    {
        return S_OK;
    }


    // IQueueCallbackDeviceIoControl

    virtual
    VOID
    STDMETHODCALLTYPE
    OnDeviceIoControl( 
        __in IWDFIoQueue *pWdfQueue,
        __in IWDFIoRequest *pWdfRequest,
        __in ULONG ControlCode,
        __in SIZE_T InputBufferSizeInBytes,
        __in SIZE_T OutputBufferSizeInBytes
        );

private:

    IWDFIoQueue *               m_FxQueue;

    CMyDevice *                 m_Device;

};

Im vorherigen Codebeispiel deklariert der Clienttreiber die Warteschlangenrückrufklasse. Bei der Instanziierung wird das Objekt mit dem Framework-Warteschlangenobjekt verknüpft, das die Art und Weise verarbeitet, wie Anforderungen an den Clienttreiber gesendet werden. Die -Klasse definiert zwei Methoden, die das Frameworkwarteschlangenobjekt erstellen und initialisieren. Die statische Methode CreateInstanceAndInitialize instanziiert die Rückrufklasse der Warteschlange und ruft dann die Initialize-Methode auf, die das Framework-Warteschlangenobjekt erstellt und initialisiert. Außerdem werden die Verteileroptionen für das Warteschlangenobjekt angegeben.

HRESULT 
CMyIoQueue::CreateInstanceAndInitialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice,
    __out CMyIoQueue** Queue
    )
{

    CComObject<CMyIoQueue> *pMyQueue = NULL;
    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    hr = CComObject<CMyIoQueue>::CreateInstance( &pMyQueue );
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to create instance %!hresult!",
                    hr);
        goto Exit;
    }

    hr = pMyQueue->Initialize(FxDevice, MyDevice);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to initialize %!hresult!",
                    hr);
        goto Exit;
    }

    *Queue = pMyQueue;

Exit:

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

Das folgende Codebeispiel zeigt die Implementierung der Initialize-Methode.

HRESULT
CMyIoQueue::Initialize(
    __in IWDFDevice *FxDevice,
    __in CMyDevice *MyDevice
    )
{
    IWDFIoQueue *fxQueue = NULL;
    HRESULT hr = S_OK;
    IUnknown *unknown = NULL;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    assert(FxDevice != NULL);
    assert(MyDevice != NULL);

    hr = this->QueryInterface(__uuidof(IUnknown), (void **)&unknown);
    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR,
                    TRACE_QUEUE,
                    "%!FUNC! Failed to query IUnknown interface %!hresult!",
                    hr);
        goto Exit;
    }

    hr = FxDevice->CreateIoQueue(unknown,
                                 FALSE,     // Default Queue?
                                 WdfIoQueueDispatchParallel,  // Dispatch type
                                 TRUE,     // Power managed?
                                 FALSE,     // Allow zero-length requests?
                                 &fxQueue); // I/O queue
    DriverSafeRelease(unknown);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to create framework queue.");
        goto Exit;
    }

    hr = FxDevice->ConfigureRequestDispatching(fxQueue,
                                               WdfRequestDeviceIoControl,
                                               TRUE);

    if (FAILED(hr))
    {
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC! Failed to configure request dispatching %!hresult!.",
                   hr);
        goto Exit;
    }

    m_FxQueue = fxQueue;
    m_Device= MyDevice;

Exit:

    DriverSafeRelease(fxQueue);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return hr;
}

Im vorherigen Codebeispiel erstellt der Clienttreiber das Framework-Warteschlangenobjekt. Das Framework stellt das Warteschlangenobjekt bereit, um den Anforderungsfluss an den Clienttreiber zu verarbeiten.

Um das Objekt zu erstellen, ruft der Clienttreiber IWDFDevice::CreateIoQueue für den IWDFDevice-Verweis auf, der in einem vorherigen Aufruf von IWDFDriver::CreateDevice abgerufen wurde.

Im IWDFDevice::CreateIoQueue-Aufruf gibt der Clienttreiber bestimmte Konfigurationsoptionen an, bevor das Framework Warteschlangen erstellt. Diese Optionen bestimmen, ob die Warteschlange energieverwaltet ist, Anforderungen der Länge Null zulässt und als Standardwarteschlange für den Treiber fungiert. Der Clienttreiber stellt diese Informationen bereit:

  • Verweis auf die zugehörige Warteschlangenrückrufklasse

    Gibt einen IUnknown-Zeiger auf seine Warteschlangenrückrufklasse an. Dadurch wird eine Partnerschaft zwischen dem Frameworkwarteschlangenobjekt und dem Warteschlangenrückrufobjekt des Clienttreibers erstellt. Wenn der E/A-Manager eine neue Anforderung von einer Anwendung empfängt, benachrichtigt er das Framework. Das Framework verwendet dann den IUnknown-Zeiger , um die öffentlichen Methoden aufzurufen, die vom Warteschlangenrückrufobjekt verfügbar gemacht werden.

  • Standard- oder sekundäre Warteschlange

    Die Warteschlange muss entweder die Standardwarteschlange oder eine sekundäre Warteschlange sein. Wenn das Frameworkwarteschlangenobjekt als Standardwarteschlange fungiert, werden der Warteschlange alle Anforderungen hinzugefügt. Eine sekundäre Warteschlange ist für einen bestimmten Anforderungstyp reserviert. Wenn der Clienttreiber eine sekundäre Warteschlange anfordert, muss der Treiber auch die IWDFDevice::ConfigureRequestDispatching-Methode aufrufen, um den Anforderungstyp anzugeben, den das Framework in die angegebene Warteschlange einfügen muss. Im Vorlagencode übergibt der Clienttreiber false im bDefaultQueue-Parameter . Dadurch wird die -Methode angewiesen, eine sekundäre Warteschlange und nicht die Standardwarteschlange zu erstellen. Später wird IWDFDevice::ConfigureRequestDispatching aufgerufen, um anzugeben, dass die Warteschlange nur Geräte-E/A-Steuerungsanforderungen enthalten darf (siehe Beispielcode in diesem Abschnitt).

  • Verteilertyp

    Der Dispatchtyp eines Warteschlangenobjekts bestimmt, wie das Framework Anforderungen an den Clienttreiber übermittelt. Der Übermittlungsmechanismus kann sequenziell, parallel oder durch einen vom Clienttreiber definierten benutzerdefinierten Mechanismus erfolgen. Bei einer sequenziellen Warteschlange wird eine Anforderung erst übermittelt, wenn der Clienttreiber die vorherige Anforderung abgeschlossen hat. Im parallelen Verteilmodus leitet das Framework die Anforderungen weiter, sobald sie vom E/A-Manager eingehen. Dies bedeutet, dass der Clienttreiber eine Anforderung empfangen kann, während eine andere verarbeitet wird. Im benutzerdefinierten Mechanismus ruft der Client die nächste Anforderung manuell aus dem Framework-Warteschlangenobjekt ab, wenn der Treiber bereit ist, sie zu verarbeiten. Im Vorlagencode fordert der Clienttreiber einen parallelen Verteilungsmodus an.

  • Energieverwaltete Warteschlange

    Das Framework-Warteschlangenobjekt muss mit dem PnP- und Energiezustand des Geräts synchronisiert werden. Wenn sich das Gerät nicht im Betriebszustand befindet, beendet das Frameworkwarteschlangenobjekt die Verteilung aller Anforderungen. Wenn sich das Gerät im Betriebszustand befindet, wird die Verteilung des Warteschlangenobjekts fortgesetzt. In einer energieverwalteten Warteschlange wird die Synchronisierung vom Framework ausgeführt. Andernfalls muss das Clientlaufwerk diese Aufgabe verarbeiten. Im Vorlagencode fordert der Client eine energieverwaltete Warteschlange an.

  • Zulässige Anforderungen der Länge "Null"

    Ein Clienttreiber kann das Framework anweisen, E/A-Anforderungen mit Puffern der Länge Null abzuschließen, anstatt sie in der Warteschlange zu platzieren. Im Vorlagencode fordert der Client das Framework an, solche Anforderungen abzuschließen.

Ein einzelnes Framework-Warteschlangenobjekt kann mehrere Arten von Anforderungen verarbeiten, z. B. Lese-, Schreib- und Geräte-E/A-Steuerelemente usw. Ein Clienttreiber, der auf dem Vorlagencode basiert, kann nur Geräte-E/A-Steuerungsanforderungen verarbeiten. Dazu implementiert die Warteschlangenrückrufklasse des Clienttreibers die IQueueCallbackDeviceIoControl-Schnittstelle und die zugehörige IQueueCallbackDeviceIoControl::OnDeviceIoControl-Methode . Dadurch kann das Framework die Implementierung von IQueueCallbackDeviceIoControl::OnDeviceIoControl durch den Clienttreiber aufrufen, wenn das Framework eine Anforderung zur Geräte-E/A-Steuerung verarbeitet.

Für andere Arten von Anforderungen muss der Clienttreiber die entsprechende IQueueCallbackXxx-Schnittstelle implementieren. Wenn der Clienttreiber beispielsweise Leseanforderungen verarbeiten möchte, muss die Warteschlangenrückrufklasse die IQueueCallbackRead-Schnittstelle und die zugehörige IQueueCallbackRead::OnRead-Methode implementieren. Informationen zu den Typen von Anforderungen und Rückrufschnittstellen finden Sie unter E/A-Warteschlangenereignisrückruffunktionen.

Das folgende Codebeispiel zeigt die IQueueCallbackDeviceIoControl::OnDeviceIoControl-Implementierung .

VOID
STDMETHODCALLTYPE
CMyIoQueue::OnDeviceIoControl(
    __in IWDFIoQueue *FxQueue,
    __in IWDFIoRequest *FxRequest,
    __in ULONG ControlCode,
    __in SIZE_T InputBufferSizeInBytes,
    __in SIZE_T OutputBufferSizeInBytes
    )
{
    UNREFERENCED_PARAMETER(FxQueue);
    UNREFERENCED_PARAMETER(ControlCode);
    UNREFERENCED_PARAMETER(InputBufferSizeInBytes);
    UNREFERENCED_PARAMETER(OutputBufferSizeInBytes);

    HRESULT hr = S_OK;

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Entry");

    if (m_Device == NULL) {
        // We don't have pointer to device object
        TraceEvents(TRACE_LEVEL_ERROR, 
                   TRACE_QUEUE, 
                   "%!FUNC!NULL pointer to device object.");
        hr = E_POINTER;
        goto Exit;
    }

    //
    // Process the IOCTLs
    //

Exit:

    FxRequest->Complete(hr);

    TraceEvents(TRACE_LEVEL_INFORMATION, TRACE_QUEUE, "%!FUNC! Exit");

    return;

}

Sehen wir uns an, wie der Warteschlangenmechanismus funktioniert. Um mit dem USB-Gerät zu kommunizieren, öffnet eine Anwendung zunächst ein Handle für das Gerät und sendet eine Geräte-E/A-Steuerungsanforderung, indem sie die DeviceIoControl-Funktion mit einem bestimmten Steuercode aufruft. Abhängig vom Typ des Steuerelementcodes kann die Anwendung Eingabe- und Ausgabepuffer in diesem Aufruf angeben. Der Aufruf wird schließlich vom E/A-Manager empfangen, der das Framework benachrichtigt. Das Framework erstellt ein Frameworkanforderungsobjekt und fügt es dem Framework-Warteschlangenobjekt hinzu. Da das Warteschlangenobjekt im Vorlagencode mit dem WdfIoQueueDispatchParallel-Flag erstellt wurde, wird der Rückruf aufgerufen, sobald die Anforderung der Warteschlange hinzugefügt wird.

Wenn das Framework den Ereignisrückruf des Clienttreibers aufruft, übergibt es ein Handle an das Framework-Anforderungsobjekt, das die von der Anwendung gesendete Anforderung (und ihre Eingabe- und Ausgabepuffer) enthält. Darüber hinaus wird ein Handle an das Frameworkwarteschlangenobjekt gesendet, das diese Anforderung enthält. Im Ereignisrückruf verarbeitet der Clienttreiber die Anforderung nach Bedarf. Der Vorlagencode schließt die Anforderung einfach ab. Der Clienttreiber kann komplexere Aufgaben ausführen. Wenn eine Anwendung für instance bestimmte Geräteinformationen anfordert, kann der Clienttreiber im Fall eines Rückrufs eine USB-Steuerungsanforderung erstellen und an den USB-Treiberstapel senden, um die angeforderten Geräteinformationen abzurufen. USB-Steuerungsanforderungen werden unter USB-Steuerungsübertragung erläutert.

Quellcode des Treibereintrags

Im Vorlagencode wird der Treibereintrag in dllsup.cpp implementiert.

Dllsup.cpp

Nach dem Abschnitt include wird eine GUID-Konstante für den Clienttreiber deklariert. Diese GUID muss mit der GUID in der Installationsdatei (INF) des Treibers übereinstimmen.

const CLSID CLSID_Driver =
{0x079e211c,0x8a82,0x4c16,{0x96,0xe2,0x2d,0x28,0xcf,0x23,0xb7,0xff}};

Der nächste Codeblock deklariert die Klassenfactory für den Clienttreiber.

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

Der Vorlagencode verwendet ATL-Unterstützung, um komplexen COM-Code zu kapseln. Die Klassenfactory erbt die Vorlagenklasse CAtlDllModuleT, die den gesamten erforderlichen Code zum Erstellen des Clienttreibers enthält.

Der folgende Codeausschnitt zeigt die Implementierung von DllMain.

extern "C"
BOOL
WINAPI
DllMain(
    HINSTANCE hInstance,
    DWORD dwReason,
    LPVOID lpReserved
    )
{
    if (dwReason == DLL_PROCESS_ATTACH) {
        WPP_INIT_TRACING(MYDRIVER_TRACING_ID);

        g_hInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);

    } else if (dwReason == DLL_PROCESS_DETACH) {
        WPP_CLEANUP();
    }

    return _AtlModule.DllMain(dwReason, lpReserved);
}

Wenn Ihr Clienttreiber die DllMain-Funktion implementiert, betrachtet Windows DllMain als Einstiegspunkt für das Clienttreibermodul. Windows ruft DllMain auf, nachdem das Clienttreibermodul in WUDFHost.exe geladen wurde. Windows ruft DllMain erneut auf, kurz bevor Windows den Clienttreiber im Arbeitsspeicher entlädt. DllMain kann globale Variablen auf Treiberebene zuordnen und freigeben. Im Vorlagencode initialisiert und gibt der Clienttreiber die für die WPP-Ablaufverfolgung erforderlichen Ressourcen frei und ruft die DllMain-Implementierung der ATL-Klasse auf.

Der folgende Codeausschnitt zeigt die Implementierung von DllGetClassObject.

STDAPI
DllGetClassObject(
    __in REFCLSID rclsid,
    __in REFIID riid,
    __deref_out LPVOID FAR* ppv
    )
{
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}

Im Vorlagencode werden die Klassenfactory und DllGetClassObject in ATL implementiert. Der vorangehende Codeausschnitt ruft einfach die ATL DllGetClassObject-Implementierung auf. Im Allgemeinen muss DllGetClassObject die folgenden Aufgaben ausführen:

  1. Stellen Sie sicher, dass die vom Framework übergebene CLSID die GUID für Ihren Clienttreiber ist. Das Framework ruft die CLSID für den Clienttreiber aus der INF-Datei des Treibers ab. Stellen Sie beim Überprüfen sicher, dass die angegebene GUID mit der in der INF angegebenen übereinstimmt.
  2. Instanziieren Sie die vom Clienttreiber implementierte Klassenfactory. Im Vorlagencode wird dies von der ATL-Klasse gekapselt.
  3. Rufen Sie einen Zeiger auf die IClassFactory-Schnittstelle der Klassenfactory ab, und geben Sie den abgerufenen Zeiger auf das Framework zurück.

Nachdem das Clienttreibermodul in den Arbeitsspeicher geladen wurde, ruft das Framework die vom Treiber bereitgestellte DllGetClassObject-Funktion auf. Im Aufruf des Frameworks für DllGetClassObject übergibt das Framework die CLSID, die den Clienttreiber identifiziert, und fordert einen Zeiger auf die IClassFactory-Schnittstelle einer Klassenfactory an. Der Clienttreiber implementiert die Klassenfactory, die die Erstellung des Treiberrückrufs erleichtert. Daher muss Ihr Clienttreiber mindestens eine Klassenfactory enthalten. Das Framework ruft dann IClassFactory::CreateInstance auf und fordert einen IDriverEntry-Zeiger auf die Treiberrückrufklasse an.

Exports.def

Damit das Framework DllGetClassObject aufrufen kann, muss der Clienttreiber die Funktion aus einer DEF-Datei exportieren. Die Datei ist bereits im Visual Studio-Projekt enthalten.

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

Im vorherigen Codeausschnitt von Export.def, der im Treiberprojekt enthalten ist, stellt der Client den Namen des Treibermoduls als LIBRARY und DllGetClassObject unter EXPORTS bereit. Weitere Informationen finden Sie unter Exportieren aus einer DLL mithilfe von DEF-Dateien.