Partager via


Présentation de la structure de code du pilote client USB (UMDF)

Dans cette rubrique, vous allez découvrir le code source d’un pilote client USB basé sur UMDF. Les exemples de code sont générés par le modèle pilote en mode utilisateur USB fourni avec Microsoft Visual Studio. Le code du modèle utilise la bibliothèque active de modèles (ATL) pour générer l’infrastructure COM. ATL et les détails sur l’implémentation COM dans le pilote client ne sont pas abordés ici.

Pour obtenir des instructions sur la génération du code du modèle UMDF, consultez Comment écrire votre premier pilote client USB (UMDF). Le code du modèle est décrit dans ces sections :

Avant d’aborder les détails du code du modèle, examinons certaines déclarations dans le fichier d’en-tête (Internal.h) qui sont pertinentes pour le développement de pilotes UMDF.

Internal.h contient ces fichiers, inclus dans le Kit de pilotes Windows (WDK) :

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

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

Atlbase.h et atlcom.h incluent des déclarations pour la prise en charge d’ATL. Chaque classe implémentée par le pilote client implémente la classe ATL publique CComObjectRootEx.

Wudfddi.h est toujours inclus pour le développement de pilotes UMDF. Le fichier d’en-tête comprend différentes déclarations et définitions de méthodes et de structures dont vous avez besoin pour compiler un pilote UMDF.

Wudfusb.h inclut des déclarations et des définitions de structures et de méthodes UMDF nécessaires pour communiquer avec les objets cibles d’E/S USB fournis par l’infrastructure.

Le bloc suivant dans Internal.h déclare une constante GUID pour l’interface de l’appareil. Les applications peuvent utiliser ce GUID pour ouvrir un handle sur l’appareil à l’aide des API SetupDiXxx . Le GUID est inscrit après que l’infrastructure a créé l’objet d’appareil.

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

La partie suivante déclare la macro de suivi et le GUID de suivi. Notez le GUID de suivi ; vous en aurez besoin pour activer le suivi.

#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)

La ligne suivante dans Internal.h forward déclare la classe implémentée par le pilote client pour l’objet de rappel de file d’attente. Il inclut également d’autres fichiers projet générés par le modèle. Les fichiers d’implémentation et d’en-tête de projet sont abordés plus loin dans cette rubrique.

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

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

Une fois le pilote client installé, Windows charge le pilote client et l’infrastructure dans un instance du processus hôte. À partir de là, le framework charge et initialise le pilote client. L’infrastructure effectue les tâches suivantes :

  1. Crée un objet de pilote dans l’infrastructure, qui représente votre pilote client.
  2. Demande un pointeur d’interface IDriverEntry à partir de la fabrique de classe.
  3. Crée un objet d’appareil dans l’infrastructure.
  4. Initialise l’objet d’appareil après le démarrage de l’appareil par le Gestionnaire PnP.

Pendant le chargement et l’initialisation du pilote, plusieurs événements se produisent et l’infrastructure permet au pilote client de participer à leur gestion. Du côté du pilote client, le pilote effectue les tâches suivantes :

  1. Implémente et exporte la fonction DllGetClassObject à partir de votre module de pilote client afin que l’infrastructure puisse obtenir une référence au pilote.
  2. Fournit une classe de rappel qui implémente l’interface IDriverEntry .
  3. Fournit une classe de rappel qui implémente les interfaces IPnpCallbackXxx .
  4. Obtient une référence à l’objet d’appareil et le configure en fonction des exigences du pilote client.

Code source du rappel de pilote

L’infrastructure crée l’objet driver, qui représente le instance du pilote client chargé par Windows. Le pilote client fournit au moins un rappel de pilote qui inscrit le pilote auprès de l’infrastructure.

Le code source complet du rappel de pilote se trouve dans Driver.h et Driver.c.

Le pilote client doit définir une classe de rappel de pilote qui implémente les interfaces IUnknown et IDriverEntry . Le fichier d’en-tête, Driver.h, déclare une classe appelée CMyDriver, qui définit le rappel du pilote.

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)

Le rappel du pilote doit être une classe COM, ce qui signifie qu’il doit implémenter IUnknown et les méthodes associées. Dans le code du modèle, les classes ATL CComObjectRootEx et CComCoClass contiennent les méthodes IUnknown .

Une fois que Windows a instancié le processus hôte, l’infrastructure crée l’objet pilote. Pour ce faire, l’infrastructure crée une instance de la classe de rappel de pilote et appelle l’implémentation des pilotes de DllGetClassObject (décrite dans la section Code source d’entrée du pilote) et pour obtenir le pointeur d’interface IDriverEntry du pilote client. Cet appel inscrit l’objet de rappel de pilote avec l’objet pilote d’infrastructure. Une fois l’inscription réussie, l’infrastructure appelle l’implémentation du pilote client lorsque certains événements spécifiques au pilote se produisent. La première méthode appelée par le framework est la méthode IDriverEntry ::OnInitialize . Dans l’implémentation du pilote client de IDriverEntry ::OnInitialize, le pilote client peut allouer des ressources de pilote global. Ces ressources doivent être publiées dans IDriverEntry ::OnDeinitialize appelé par le framework juste avant qu’il ne se prépare à décharger le pilote client. Le code du modèle fournit une implémentation minimale pour les méthodes OnInitialize et OnDeinitialize .

La méthode la plus importante de IDriverEntry est IDriverEntry ::OnDeviceAdd. Avant que l’infrastructure ne crée l’objet de périphérique d’infrastructure (décrit dans la section suivante), elle appelle l’implémentation IDriverEntry ::OnDeviceAdd du pilote. Lors de l’appel de la méthode, l’infrastructure transmet un pointeur IWDFDriver à l’objet pilote et un pointeur IWDFDeviceInitialize . Le pilote client peut appeler des méthodes IWDFDeviceInitialize pour spécifier certaines options de configuration.

En règle générale, le pilote client effectue les tâches suivantes dans son implémentation IDriverEntry ::OnDeviceAdd :

  • Spécifie les informations de configuration de l’objet d’appareil à créer.
  • Instancie la classe de rappel de périphérique du pilote.
  • Crée l’objet d’appareil framework et inscrit son objet de rappel d’appareil auprès de l’infrastructure.
  • Initialise l’objet d’appareil framework.
  • Inscrit le GUID de l’interface de périphérique du pilote client.

Dans le code du modèle, IDriverEntry ::OnDeviceAdd appelle une méthode statique, CMyDevice ::CreateInstanceAndInitialize, définie dans la classe de rappel d’appareil. La méthode statique instancie d’abord la classe de rappel de périphérique du pilote client, puis crée l’objet de périphérique d’infrastructure. La classe de rappel d’appareil définit également une méthode publique nommée Configure qui effectue les tâches restantes mentionnées dans la liste précédente. L’implémentation de la classe de rappel d’appareil est décrite dans la section suivante. L’exemple de code suivant montre l’implémentation IDriverEntry ::OnDeviceAdd dans le code du modèle.

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

L’exemple de code suivant montre la déclaration de classe d’appareil dans 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
        );

};

Code source du rappel d’appareil

L’objet d’appareil framework est un instance de la classe framework qui représente l’objet de périphérique chargé dans la pile de périphériques du pilote client. Pour plus d’informations sur les fonctionnalités d’un objet d’appareil, consultez Nœuds d’appareil et piles d’appareils.

Le code source complet de l’objet d’appareil se trouve dans Device.h et Device.c.

La classe d’appareil framework implémente l’interface IWDFDevice . Le pilote client est chargé de créer un instance de cette classe dans l’implémentation du pilote de IDriverEntry ::OnDeviceAdd. Une fois l’objet créé, le pilote client obtient un pointeur IWDFDevice vers le nouvel objet et appelle des méthodes sur cette interface pour gérer les opérations de l’objet périphérique.

Implémentation de IDriverEntry ::OnDeviceAdd

Dans la section précédente, vous avez brièvement vu les tâches qu’un pilote client effectue dans IDriverEntry ::OnDeviceAdd. Voici plus d’informations sur ces tâches. Le pilote client :

  • Spécifie les informations de configuration de l’objet d’appareil à créer.

    Dans l’appel de framework à l’implémentation du pilote client de la méthode IDriverEntry ::OnDeviceAdd , le framework transmet un pointeur IWDFDeviceInitialize . Le pilote client utilise ce pointeur pour spécifier les informations de configuration de l’objet de périphérique à créer. Par exemple, le pilote client spécifie si le pilote client est un filtre ou un pilote de fonction. Pour identifier le pilote client en tant que pilote de filtre, il appelle IWDFDeviceInitialize ::SetFilter. Dans ce cas, l’infrastructure crée un objet d’appareil de filtre (FiDO) ; sinon, un objet de périphérique de fonction (FDO) est créé. Une autre option que vous pouvez définir est le mode de synchronisation en appelant IWDFDeviceInitialize ::SetLockingConstraint.

  • Appelle la méthode IWDFDriver ::CreateDevice en passant le pointeur d’interface IWDFDeviceInitialize , une référence IUnknown de l’objet de rappel d’appareil et une variable IWDFDevice pointeur vers pointeur.

    Si l’appel IWDFDriver ::CreateDevice réussit :

    • L’infrastructure crée l’objet d’appareil.

    • L’infrastructure enregistre le rappel d’appareil auprès de l’infrastructure.

      Une fois le rappel d’appareil associé à l’objet de périphérique d’infrastructure, l’infrastructure et le pilote client gèrent certains événements, tels que les changements d’état PnP et d’alimentation. Par exemple, lorsque le Gestionnaire PnP démarre l’appareil, l’infrastructure est avertie. L’infrastructure appelle ensuite l’implémentation IPnpCallbackHardware ::OnPrepareHardware du rappel d’appareil. Chaque pilote client doit inscrire au moins un objet de rappel de périphérique.

    • Le pilote client reçoit l’adresse du nouvel objet d’appareil dans la variable IWDFDevice . Lors de la réception d’un pointeur vers l’objet d’appareil framework, le pilote client peut effectuer des tâches d’initialisation, telles que la configuration des files d’attente pour le flux d’E/S et l’inscription du GUID d’interface de l’appareil.

  • Appelle IWDFDevice ::CreateDeviceInterface pour inscrire le GUID d’interface de périphérique du pilote client. Les applications peuvent utiliser le GUID pour envoyer des requêtes au pilote client. La constante GUID est déclarée dans Internal.h.

  • Initialise les files d’attente pour les transferts d’E/S vers et depuis l’appareil.

Le code du modèle définit la méthode d’assistance Initialize, qui spécifie les informations de configuration et crée l’objet d’appareil.

L’exemple de code suivant montre les implémentations pour 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;
}

Dans l’exemple de code précédent, le pilote client crée l’objet d’appareil et inscrit son rappel d’appareil. Avant de créer l’objet d’appareil, le pilote spécifie sa préférence de configuration en appelant des méthodes sur le pointeur d’interface IWDFDeviceInitialize . Il s’agit du même pointeur passé par l’infrastructure dans son appel précédent à la méthode IDriverEntry ::OnDeviceAdd du pilote client.

Le pilote client spécifie qu’il s’agit du propriétaire de la stratégie d’alimentation de l’objet d’appareil. En tant que propriétaire de la stratégie d’alimentation, le pilote client détermine l’état d’alimentation approprié que l’appareil doit entrer lorsque l’état d’alimentation du système change. Le pilote est également responsable de l’envoi des requêtes pertinentes à l’appareil afin d’effectuer la transition de l’état d’alimentation. Par défaut, un pilote client basé sur UMDF n’est pas le propriétaire de la stratégie d’alimentation ; l’infrastructure gère toutes les transitions d’état d’alimentation. L’infrastructure envoie automatiquement l’appareil à D3 lorsque le système entre en état de veille et, à l’inverse, ramène l’appareil à D0 lorsque le système entre dans l’état de fonctionnement de S0. Pour plus d’informations, consultez Propriété power policy dans UMDF.

Une autre option de configuration consiste à spécifier si le pilote client est le pilote de filtre ou le pilote de fonction pour l’appareil. Notez que dans l’exemple de code, le pilote client ne spécifie pas explicitement sa préférence. Cela signifie que le pilote client est le pilote de fonction et que l’infrastructure doit créer un FDO dans la pile des appareils. Si le pilote client veut être le pilote de filtre, il doit appeler la méthode IWDFDeviceInitialize ::SetFilter . Dans ce cas, l’infrastructure crée un FiDO dans la pile des appareils.

Le pilote client spécifie également qu’aucun des appels de l’infrastructure aux rappels du pilote client n’est synchronisé. Le pilote client gère toutes les tâches de synchronisation. Pour spécifier cette préférence, le pilote client appelle la méthode IWDFDeviceInitialize ::SetLockingConstraint .

Ensuite, le pilote client obtient un pointeur IUnknown vers sa classe de rappel d’appareil en appelant IUnknown ::QueryInterface. Par la suite, le pilote client appelle IWDFDriver ::CreateDevice, ce qui crée l’objet d’appareil framework et inscrit le rappel d’appareil du pilote client à l’aide du pointeur IUnknown .

Notez que le pilote client stocke l’adresse de l’objet d’appareil (reçue via l’appel IWDFDriver ::CreateDevice ) dans un membre de données privé de la classe de rappel d’appareil, puis libère cette référence en appelant DriverSafeRelease (fonction inline définie dans Internal.h). En effet, la durée de vie de l’objet d’appareil est suivie par l’infrastructure. Par conséquent, le pilote client n’est pas obligé de conserver le nombre de références supplémentaires de l’objet d’appareil.

Le code du modèle définit la méthode publique Configure, qui inscrit le GUID d’interface de l’appareil et configure les files d’attente. L’exemple de code suivant montre la définition de la méthode Configure dans la classe de rappel d’appareil, CMyDevice. Configurer est appelé par IDriverEntry ::OnDeviceAdd après la création de l’objet d’appareil framework.

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

Dans l’exemple de code précédent, le pilote client effectue deux tâches main : initialiser les files d’attente pour le flux d’E/S et inscrire le GUID d’interface de périphérique.

Les files d’attente sont créées et configurées dans la classe CMyIoQueue. La première tâche consiste à instancier cette classe en appelant la méthode statique nommée CreateInstanceAndInitialize. Le pilote client appelle Configurer pour initialiser les files d’attente. CreateInstanceAndInitialize et Configure sont déclarés dans CMyIoQueue, qui est abordé plus loin dans cette rubrique.

Le pilote client appelle également IWDFDevice ::CreateDeviceInterface pour inscrire le GUID d’interface de périphérique du pilote client. Les applications peuvent utiliser le GUID pour envoyer des requêtes au pilote client. La constante GUID est déclarée dans Internal.h.

Implémentation IPnpCallbackHardware et tâches spécifiques à l’USB

Examinons ensuite l’implémentation de l’interface IPnpCallbackHardware dans Device.cpp.

Chaque classe de rappel d’appareil doit implémenter l’interface IPnpCallbackHardware . Cette interface comporte deux méthodes : IPnpCallbackHardware ::OnPrepareHardware et IPnpCallbackHardware ::OnReleaseHardware. L’infrastructure appelle ces méthodes en réponse à deux événements : quand le Gestionnaire PnP démarre l’appareil et quand il supprime l’appareil. Lorsqu’un appareil est démarré, la communication avec le matériel est établie, mais l’appareil n’est pas entré en état d’utilisation (D0). Par conséquent, dans IPnpCallbackHardware ::OnPrepareHardware , le pilote client peut obtenir des informations sur l’appareil à partir du matériel, allouer des ressources et initialiser les objets d’infrastructure requis pendant la durée de vie du pilote. Lorsque le Gestionnaire PnP supprime l’appareil, le pilote est déchargé du système. L’infrastructure appelle l’implémentation IPnpCallbackHardware ::OnReleaseHardware du pilote client dans laquelle le pilote peut libérer ces ressources et objets d’infrastructure.

Le Gestionnaire PnP peut générer d’autres types d’événements qui résultent des changements d’état PnP. L’infrastructure fournit la gestion par défaut de ces événements. Le pilote client peut choisir de participer à la gestion de ces événements. Envisagez un scénario où le périphérique USB est détaché de l’hôte. Le Gestionnaire PnP reconnaît cet événement et avertit l’infrastructure. Si le pilote client souhaite effectuer des tâches supplémentaires en réponse à l’événement, il doit implémenter l’interface IPnpCallback et la méthode IPnpCallback ::OnSurpriseRemoval associée dans la classe de rappel d’appareil. Sinon, l’infrastructure continue avec sa gestion par défaut de l’événement.

Un pilote client USB doit récupérer des informations sur les interfaces prises en charge, les autres paramètres et les points de terminaison, et les configurer avant d’envoyer des demandes d’E/S pour le transfert de données. UMDF fournit des objets cibles d’E/S spécialisés qui simplifient de nombreuses tâches de configuration pour le pilote client. Pour configurer un périphérique USB, le pilote client a besoin d’informations sur le périphérique qui sont disponibles uniquement après le démarrage du gestionnaire PnP.

Ce code de modèle crée ces objets dans la méthode IPnpCallbackHardware ::OnPrepareHardware .

En règle générale, le pilote client effectue une ou plusieurs de ces tâches de configuration (selon la conception de l’appareil) :

  1. Récupère des informations sur la configuration actuelle, telles que le nombre d’interfaces. L’infrastructure sélectionne la première configuration sur un périphérique USB. Le pilote client ne peut pas sélectionner une autre configuration dans le cas d’appareils à configuration multiple.
  2. Récupère des informations sur les interfaces, telles que le nombre de points de terminaison.
  3. Modifie le paramètre de remplacement dans chaque interface, si l’interface prend en charge plusieurs paramètres. Par défaut, l’infrastructure sélectionne le premier autre paramètre de chaque interface dans la première configuration sur un périphérique USB. Le pilote client peut choisir de sélectionner un autre paramètre.
  4. Récupère des informations sur les points de terminaison dans chaque interface.

Pour effectuer ces tâches, le pilote client peut utiliser ces types d’objets cibles d’E/S USB spécialisés fournis par le WDF.

Objet cible d’E/S USB Description Interface UMDF
Objet d’appareil cible Représente un périphérique USB et fournit des méthodes permettant de récupérer le descripteur d’appareil et d’envoyer des demandes de contrôle à l’appareil. IWDFUsbTargetDevice
Objet d’interface cible Représente une interface individuelle et fournit des méthodes qu’un pilote client peut appeler pour sélectionner un autre paramètre et récupérer des informations sur le paramètre. IWDFUsbInterface
Objet de canal cible Représente un canal individuel pour un point de terminaison configuré dans le paramètre alternatif actuel pour une interface. Le pilote de bus USB sélectionne chaque interface dans la configuration sélectionnée et configure un canal de communication vers chaque point de terminaison au sein de l’interface. Dans la terminologie USB, ce canal de communication est appelé un canal. IWDFUsbTargetPipe

L’exemple de code suivant montre l’implémentation pour 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;
}

Pour utiliser les objets cibles d’E/S USB de l’infrastructure, le pilote client doit d’abord créer l’objet de périphérique cible USB. Dans le modèle objet framework, l’objet périphérique cible USB est un enfant de l’objet d’appareil qui représente un périphérique USB. L’objet périphérique cible USB est implémenté par l’infrastructure et effectue toutes les tâches au niveau de l’appareil d’un périphérique USB, telles que la sélection d’une configuration.

Dans l’exemple de code précédent, le pilote client interroge l’objet de périphérique framework et obtient un pointeur IWDFUsbTargetFactory vers la fabrique de classe qui crée l’objet périphérique cible USB. À l’aide de ce pointeur, le pilote client appelle la méthode IWDFUsbTargetDevice ::CreateUsbTargetDevice . La méthode crée l’objet périphérique cible USB et retourne un pointeur vers l’interface IWDFUsbTargetDevice . La méthode sélectionne également la configuration par défaut (première) et l’autre paramètre 0 pour chaque interface de cette configuration.

Le code du modèle stocke l’adresse de l’objet périphérique cible USB (reçu via l’appel IWDFDriver ::CreateDevice ) dans un membre de données privé de la classe de rappel d’appareil, puis libère cette référence en appelant DriverSafeRelease. Le nombre de références de l’objet périphérique cible USB est géré par l’infrastructure. L’objet est actif tant que l’objet d’appareil est actif. Le pilote client doit publier la référence dans IPnpCallbackHardware ::OnReleaseHardware.

Une fois que le pilote client a créé l’objet de périphérique cible USB, le pilote appelle les méthodes IWDFUsbTargetDevice pour effectuer les tâches suivantes :

  • Récupérez l’appareil, la configuration, les descripteurs d’interface et d’autres informations telles que la vitesse de l’appareil.
  • Mettre en forme et envoyer des demandes de contrôle d’E/S au point de terminaison par défaut.
  • Définissez la stratégie d’alimentation pour l’ensemble du périphérique USB.

Pour plus d’informations, consultez Utilisation des périphériques USB dans UMDF. L’exemple de code suivant montre l’implémentation de 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;
}

Code source de la file d’attente

L’objet de file d’attente d’infrastructure représente la file d’attente d’E/S d’un objet d’appareil d’infrastructure spécifique. Le code source complet de l’objet file d’attente se trouve dans IoQueue.h et IoQueue.c.

IoQueue.h

Le fichier d’en-tête IoQueue.h déclare la classe de rappel de file d’attente.

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;

};

Dans l’exemple de code précédent, le pilote client déclare la classe de rappel de file d’attente. Lorsqu’il est instancié, l’objet est associé à l’objet de file d’attente du framework qui gère la façon dont les demandes sont distribuées au pilote client. La classe définit deux méthodes qui créent et initialisent l’objet de file d’attente du framework. La méthode statique CreateInstanceAndInitialize instancie la classe de rappel de file d’attente, puis appelle la méthode Initialize qui crée et initialise l’objet de file d’attente du framework. Il spécifie également les options de répartition de l’objet file d’attente.

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

L’exemple de code suivant montre l’implémentation de la méthode Initialize.

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

Dans l’exemple de code précédent, le pilote client crée l’objet de file d’attente du framework. L’infrastructure fournit l’objet file d’attente pour gérer le flux de requête vers le pilote client.

Pour créer l’objet, le pilote client appelle IWDFDevice ::CreateIoQueue sur la référence IWDFDevice obtenue lors d’un appel précédent à IWDFDriver ::CreateDevice.

Dans l’appel IWDFDevice ::CreateIoQueue , le pilote client spécifie certaines options de configuration avant que l’infrastructure crée des files d’attente. Ces options déterminent si la file d’attente est gérée par l’alimentation, autorise les requêtes de longueur nulle et fait office de file d’attente par défaut pour le pilote. Le pilote client fournit cet ensemble d’informations :

  • Référence à sa classe de rappel de file d’attente

    Spécifie un pointeur IUnknown vers sa classe de rappel de file d’attente. Cela crée un partenariat entre l’objet de file d’attente du framework et l’objet de rappel de file d’attente du pilote client. Lorsque le gestionnaire d’E/S reçoit une nouvelle demande d’une application, il en avertit l’infrastructure. L’infrastructure utilise ensuite le pointeur IUnknown pour appeler les méthodes publiques exposées par l’objet de rappel de file d’attente.

  • File d’attente par défaut ou secondaire

    La file d’attente doit être la file d’attente par défaut ou une file d’attente secondaire. Si l’objet de file d’attente du framework agit comme file d’attente par défaut, toutes les demandes sont ajoutées à la file d’attente. Une file d’attente secondaire est dédiée à un type spécifique de requête. Si le pilote client demande une file d’attente secondaire, il doit également appeler la méthode IWDFDevice ::ConfigureRequestDispatching pour indiquer le type de requête que le framework doit placer dans la file d’attente spécifiée. Dans le code du modèle, le pilote client transmet FALSE dans le paramètre bDefaultQueue . Cela indique à la méthode de créer une file d’attente secondaire et non la file d’attente par défaut. Il appelle plus tard IWDFDevice ::ConfigureRequestDispatching pour indiquer que la file d’attente doit contenir uniquement des demandes de contrôle d’E/S d’appareil (voir l’exemple de code dans cette section).

  • Type de répartition

    Le type de dispatch d’un objet file d’attente détermine la façon dont l’infrastructure remet les requêtes au pilote client. Le mécanisme de remise peut être séquentiel, en parallèle ou par un mécanisme personnalisé défini par le pilote client. Pour une file d’attente séquentielle, une requête n’est pas remise tant que le pilote client n’a pas terminé la requête précédente. En mode de répartition parallèle, l’infrastructure transfère les demandes dès qu’elles arrivent du Gestionnaire d’E/S. Cela signifie que le pilote client peut recevoir une demande lors du traitement d’une autre. Dans le mécanisme personnalisé, le client extrait manuellement la requête suivante de l’objet de file d’attente du framework, lorsque le pilote est prêt à la traiter. Dans le code du modèle, le pilote client demande un mode de répartition parallèle.

  • File d’attente gérée par l’alimentation

    L’objet de file d’attente du framework doit être synchronisé avec le PnP et l’état d’alimentation de l’appareil. Si l’appareil n’est pas à l’état Opérationnel, l’objet de file d’attente du framework cesse de distribuer toutes les requêtes. Lorsque l’appareil est à l’état Fonctionnement, l’objet file d’attente reprend la distribution. Dans une file d’attente gérée par l’alimentation, la synchronisation est effectuée par le framework ; sinon, le lecteur client doit gérer cette tâche. Dans le code du modèle, le client demande une file d’attente gérée par l’alimentation.

  • Demandes de longueur nulle autorisées

    Un pilote client peut demander à l’infrastructure de terminer les demandes d’E/S avec des mémoires tampons de longueur nulle au lieu de les placer dans la file d’attente. Dans le code du modèle, le client demande à l’infrastructure d’effectuer ces demandes.

Un objet de file d’attente d’infrastructure unique peut gérer plusieurs types de demandes, tels que la lecture, l’écriture et le contrôle d’E/S d’appareil, etc. Un pilote client basé sur le code du modèle peut traiter uniquement les demandes de contrôle d’E/S d’appareil. Pour cela, la classe de rappel de file d’attente du pilote client implémente l’interface IQueueCallbackDeviceIoControl et sa méthode IQueueCallbackDeviceIoControl ::OnDeviceIoControl . Cela permet à l’infrastructure d’appeler l’implémentation du pilote client de IQueueCallbackDeviceIoControl ::OnDeviceIoControl quand l’infrastructure traite une demande de contrôle d’E/S d’appareil.

Pour les autres types de requêtes, le pilote client doit implémenter l’interface IQueueCallbackXxx correspondante. Par exemple, si le pilote client souhaite gérer les demandes de lecture, la classe de rappel de file d’attente doit implémenter l’interface IQueueCallbackRead et sa méthode IQueueCallbackRead ::OnRead . Pour plus d’informations sur les types de requêtes et d’interfaces de rappel, consultez Fonctions de rappel d’événements de file d’attente d’E/S.

L’exemple de code suivant montre l’implémentation IQueueCallbackDeviceIoControl ::OnDeviceIoControl .

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;

}

Voyons comment fonctionne le mécanisme de file d’attente. Pour communiquer avec le périphérique USB, une application ouvre d’abord un handle à l’appareil et envoie une demande de contrôle d’E/S de périphérique en appelant la fonction DeviceIoControl avec un code de contrôle spécifique. Selon le type de code de contrôle, l’application peut spécifier des tampons d’entrée et de sortie dans cet appel. L’appel est finalement reçu par le gestionnaire d’E/S, qui avertit l’infrastructure. L’infrastructure crée un objet de requête d’infrastructure et l’ajoute à l’objet de file d’attente du framework. Dans le code du modèle, étant donné que l’objet file d’attente a été créé avec l’indicateur WdfIoQueueDispatchParallel, le rappel est appelé dès que la demande est ajoutée à la file d’attente.

Lorsque l’infrastructure appelle le rappel d’événement du pilote client, elle transmet un handle à l’objet de demande d’infrastructure qui contient la requête (et ses mémoires tampons d’entrée et de sortie) envoyées par l’application. En outre, il envoie un handle à l’objet de file d’attente du framework qui contient cette demande. Dans le rappel d’événement, le pilote client traite la demande en fonction des besoins. Le code du modèle termine simplement la requête. Le pilote client peut effectuer des tâches plus impliquées. Par instance, si une application demande certaines informations d’appareil, dans le rappel d’événement, le pilote client peut créer une demande de contrôle USB et l’envoyer à la pile des pilotes USB pour récupérer les informations de périphérique demandées. Les demandes de contrôle USB sont traitées dans Transfert de contrôle USB.

Code source de l’entrée du pilote

Dans le code du modèle, l’entrée du pilote est implémentée dans dllsup.cpp.

Dllsup.cpp

Après la section include, une constante GUID pour le pilote client est déclarée. Ce GUID doit correspondre au GUID dans le fichier d’installation (INF) du pilote.

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

Le bloc de code suivant déclare la fabrique de classe pour le pilote client.

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

Le code de modèle utilise la prise en charge d’ATL pour encapsuler du code COM complexe. La fabrique de classes hérite de la classe de modèle CAtlDllModuleT qui contient tout le code nécessaire à la création du pilote client.

L’extrait de code suivant montre l’implémentation de 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);
}

Si votre pilote client implémente la fonction DllMain , Windows considère DllMain comme le point d’entrée du module de pilote client. Windows appelle DllMain après avoir chargé le module de pilote client dans WUDFHost.exe. Windows appelle à nouveau DllMain juste avant que Windows décharge le pilote client en mémoire. DllMain peut allouer et libérer des variables globales au niveau du pilote. Dans le code du modèle, le pilote client initialise et libère les ressources nécessaires au suivi WPP et appelle l’implémentation DllMain de la classe ATL.

L’extrait de code suivant montre l’implémentation de DllGetClassObject.

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

Dans le code du modèle, la fabrique de classe et DllGetClassObject sont implémentés dans ATL. L’extrait de code précédent appelle simplement l’implémentation d’ATL DllGetClassObject . En général, DllGetClassObject doit effectuer les tâches suivantes :

  1. Vérifiez que le CLSID passé par l’infrastructure est le GUID de votre pilote client. L’infrastructure récupère le CLSID pour le pilote client à partir du fichier INF du pilote. Lors de la validation, assurez-vous que le GUID spécifié correspond à celui que vous avez fourni dans l’INF.
  2. Instanciez la fabrique de classes implémentée par le pilote client. Dans le code du modèle, cela est encapsulé par la classe ATL.
  3. Obtenez un pointeur vers l’interface IClassFactory de la fabrique de classes et retournez le pointeur récupéré vers l’infrastructure.

Une fois le module de pilote client chargé en mémoire, le framework appelle la fonction DllGetClassObject fournie par le pilote. Dans l’appel de l’infrastructure à DllGetClassObject, l’infrastructure transmet le CLSID qui identifie le pilote client et demande un pointeur vers l’interface IClassFactory d’une fabrique de classes. Le pilote client implémente la fabrique de classes qui facilite la création du rappel de pilote. Par conséquent, votre pilote client doit contenir au moins une fabrique de classe. Le framework appelle ensuite IClassFactory ::CreateInstance et demande un pointeur IDriverEntry vers la classe de rappel du pilote.

Exports.def

Pour que l’infrastructure appelle DllGetClassObject, le pilote client doit exporter la fonction à partir d’un fichier .def. Le fichier est déjà inclus dans le projet Visual Studio.

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

Dans l’extrait de code précédent d’Export.def inclus dans le projet de pilote, le client fournit le nom du module de pilote en tant que LIBRARY et DllGetClassObject sous EXPORTATIONS. Pour plus d’informations, consultez Exportation à partir d’une DLL à l’aide de fichiers DEF.