Condividi tramite


Informazioni sulla struttura del codice del driver client USB (UMDF)

In questo argomento si apprenderà il codice sorgente per un driver client USB basato su UMDF. Gli esempi di codice vengono generati dal modello di driver in modalità utente USB incluso in Microsoft Visual Studio. Il codice modello usa Active Template Library (ATL) per generare l'infrastruttura COM. ATL e i dettagli sull'implementazione COM nel driver client non vengono illustrati qui.

Per istruzioni sulla generazione del codice modello UMDF, vedere Come scrivere il primo driver client USB (UMDF). Il codice modello viene descritto in queste sezioni:

Prima di discutere i dettagli del codice modello, esaminiamo alcune dichiarazioni nel file di intestazione (Internal.h) rilevanti per lo sviluppo di driver UMDF.

Internal.h contiene questi file, inclusi in Windows Driver Kit (WDK):

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

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

Atlbase.h e atlcom.h includono dichiarazioni per il supporto di ATL. Ogni classe implementata dal driver client implementa la classe ATL pubblica CComObjectRootEx.

Wudfddi.h è sempre incluso per lo sviluppo di driver UMDF. Il file di intestazione include varie dichiarazioni e definizioni di metodi e strutture che è necessario compilare un driver UMDF.

Wudfusb.h include dichiarazioni e definizioni di strutture e metodi UMDF necessari per comunicare con gli oggetti di destinazione di I/O USB forniti dal framework.

Il blocco successivo in Internal.h dichiara una costante GUID per l'interfaccia del dispositivo. Le applicazioni possono usare questo GUID per aprire un handle al dispositivo usando le API SetupDiXxx . Il GUID viene registrato dopo che il framework crea l'oggetto device.

// 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 parte successiva dichiara la macro di traccia e il GUID di traccia. Prendere nota del GUID di traccia; è necessario per abilitare la traccia.

#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 riga successiva in Internal.h forward dichiara la classe implementata dal driver client per l'oggetto callback della coda. Include anche altri file di progetto generati dal modello. I file di implementazione e intestazione del progetto vengono illustrati più avanti in questo argomento.

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

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

Dopo l'installazione del driver client, Windows carica il driver client e il framework in un'istanza del processo host. Da qui il framework carica e inizializza il driver client. Il framework esegue queste attività:

  1. Crea un oggetto driver nel framework, che rappresenta il driver client.
  2. Richiede un puntatore dell'interfaccia IDriverEntry dalla classe factory.
  3. Crea un oggetto dispositivo nel framework.
  4. Inizializza l'oggetto dispositivo dopo l'avvio del dispositivo da parte di PnP Manager.

Durante il caricamento e l'inizializzazione del driver, si verificano diversi eventi e il framework consente al driver client di partecipare alla gestione. Sul lato del driver client, il driver esegue queste attività:

  1. Implementa ed esporta la funzione DllGetClassObject dal modulo driver client in modo che il framework possa ottenere un riferimento al driver.
  2. Fornisce una classe di callback che implementa l'interfaccia IDriverEntry .
  3. Fornisce una classe di callback che implementa interfacce IPnpCallbackXxx .
  4. Ottiene un riferimento all'oggetto dispositivo e lo configura in base ai requisiti del driver client.

Codice sorgente del callback del driver

Il framework crea l'oggetto driver, che rappresenta l'istanza del driver client caricata da Windows. Il driver client fornisce almeno un callback driver che registra il driver con il framework.

Il codice sorgente completo per il callback del driver è in Driver.h e Driver.c.

Il driver client deve definire una classe di callback driver che implementa interfacce IUnknown e IDriverEntry . Il file di intestazione, Driver.h, dichiara una classe denominata CMyDriver, che definisce il callback del driver.

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)

Il callback del driver deve essere una classe COM, ovvero deve implementare IUnknown e i metodi correlati. Nel codice modello, le classi ATL CComObjectRootEx e CComCoClass contengono i metodi IUnknown .

Dopo che Windows crea un'istanza del processo host, il framework crea l'oggetto driver. A tale scopo, il framework crea un'istanza della classe di callback driver e chiama l'implementazione dei driver di DllGetClassObject (descritta nella sezione Codice sorgente della voce driver ) e per ottenere il puntatore dell'interfaccia IDriverEntry del driver client. Tale chiamata registra l'oggetto callback driver con l'oggetto driver framework. Al termine della registrazione, il framework richiama l'implementazione del driver client quando si verificano determinati eventi specifici del driver. Il primo metodo richiamato dal framework è il metodo IDriverEntry::OnInitialize . Nell'implementazione del driver client di IDriverEntry::OnInitialize, il driver client può allocare risorse del driver globale. Tali risorse devono essere rilasciate in IDriverEntry::OnDeinitialize richiamate dal framework appena prima di prepararsi a scaricare il driver client. Il codice modello fornisce un'implementazione minima per i metodi OnInitialize e OnDeinitialize .

Il metodo più importante di IDriverEntry è IDriverEntry::OnDeviceAdd. Prima che il framework crei l'oggetto dispositivo framework (illustrato nella sezione successiva), chiama l'implementazione IDriverEntry::OnDeviceAdd . Quando si chiama il metodo, il framework passa un puntatore IWDFDriver all'oggetto driver e un puntatore IWDFDeviceInitialize . Il driver client può chiamare metodi IWDFDeviceInitialize per specificare determinate opzioni di configurazione.

In genere, il driver client esegue le attività seguenti nella relativa implementazione IDriverEntry::OnDeviceAdd :

  • Specifica le informazioni di configurazione per la creazione dell'oggetto dispositivo.
  • Crea un'istanza della classe di callback del dispositivo del driver.
  • Crea l'oggetto dispositivo framework e registra l'oggetto callback del dispositivo con il framework.
  • Inizializza l'oggetto dispositivo framework.
  • Registra il GUID dell'interfaccia del dispositivo del driver client.

Nel codice modello IDriverEntry::OnDeviceAdd chiama un metodo statico, CMyDevice::CreateInstanceAndInitialize, definito nella classe di callback del dispositivo. Il metodo statico crea prima un'istanza della classe di callback del dispositivo del driver client e quindi crea l'oggetto dispositivo framework. La classe di callback del dispositivo definisce anche un metodo pubblico denominato Configure che esegue attività rimanenti indicate nell'elenco precedente. L'implementazione della classe di callback del dispositivo viene illustrata nella sezione successiva. Nell'esempio di codice seguente viene illustrata l'implementazione di IDriverEntry::OnDeviceAdd nel codice del modello.

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

Nell'esempio di codice seguente viene illustrata la dichiarazione di classe del dispositivo 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
        );

};

Codice sorgente del callback del dispositivo

L'oggetto dispositivo framework è un'istanza della classe framework che rappresenta l'oggetto dispositivo caricato nello stack di dispositivi del driver client. Per informazioni sulla funzionalità di un oggetto dispositivo, vedere Nodi del dispositivo e Stack di dispositivi.

Il codice sorgente completo per l'oggetto dispositivo si trova in Device.h e Device.c.

La classe del dispositivo framework implementa l'interfaccia IWDFDevice . Il driver client è responsabile della creazione di un'istanza di tale classe nell'implementazione del driver di IDriverEntry::OnDeviceAdd. Dopo aver creato l'oggetto, il driver client ottiene un puntatore IWDFDevice al nuovo oggetto e chiama i metodi su tale interfaccia per gestire le operazioni dell'oggetto dispositivo.

IDriverEntry::OnDeviceAggiungi implementazione

Nella sezione precedente sono state visualizzate brevemente le attività eseguite da un driver client in IDriverEntry::OnDeviceAdd. Ecco altre informazioni su queste attività. Driver client:

  • Specifica le informazioni di configurazione per la creazione dell'oggetto dispositivo.

    Nella chiamata del framework all'implementazione del driver client del metodo IDriverEntry::OnDeviceAdd , il framework passa un puntatore IWDFDeviceInitialize . Il driver client usa questo puntatore per specificare le informazioni di configurazione per la creazione dell'oggetto dispositivo. Ad esempio, il driver client specifica se il driver client è un filtro o un driver di funzione. Per identificare il driver client come driver di filtro, chiama IWDFDeviceInitialize::SetFilter. In tal caso, il framework crea un oggetto del dispositivo di filtro (FiDO); in caso contrario, viene creato un oggetto dispositivo funzione (FDO). Un'altra opzione che è possibile impostare è la modalità di sincronizzazione chiamando IWDFDeviceInitialize::SetLockingConstraint.

  • Chiama il metodo IWDFDriver::CreateDevice passando il puntatore dell'interfaccia IWDFDeviceInitialize , un riferimento IUnknown dell'oggetto callback del dispositivo e una variabile IWDFDevice puntatore a puntatore.

    Se la chiamata IWDFDriver::CreateDevice ha esito positivo:

    • Il framework crea l'oggetto dispositivo.

    • Il framework registra il callback del dispositivo con il framework.

      Dopo che il callback del dispositivo viene associato all'oggetto dispositivo framework, il framework e il driver client gestiscono determinati eventi, ad esempio lo stato PnP e le modifiche dello stato di alimentazione. Ad esempio, quando PnP Manager avvia il dispositivo, il framework riceve una notifica. Il framework richiama quindi l'implementazione IPnpCallbackHardware::OnPrepareHardware del dispositivo. Ogni driver client deve registrare almeno un oggetto callback del dispositivo.

    • Il driver client riceve l'indirizzo del nuovo oggetto dispositivo nella variabile IWDFDevice . Quando si riceve un puntatore all'oggetto dispositivo framework, il driver client può procedere con le attività di inizializzazione, ad esempio la configurazione delle code per il flusso di I/O e la registrazione del GUID dell'interfaccia del dispositivo.

  • Chiama IWDFDevice::CreateDeviceInterface per registrare il GUID dell'interfaccia del dispositivo del driver client. Le applicazioni possono usare il GUID per inviare richieste al driver client. La costante GUID viene dichiarata in Internal.h.

  • Inizializza le code per i trasferimenti di I/O da e verso il dispositivo.

Il codice modello definisce il metodo helper Initialize, che specifica le informazioni di configurazione e crea l'oggetto dispositivo.

L'esempio di codice seguente mostra le implementazioni per 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;
}

Nell'esempio di codice precedente, il driver client crea l'oggetto dispositivo e registra il callback del dispositivo. Prima di creare l'oggetto dispositivo, il driver specifica la preferenza di configurazione chiamando i metodi nel puntatore a interfaccia IWDFDeviceInitialize . Si tratta dello stesso puntatore passato dal framework nella chiamata precedente al metodo IDriverEntry::OnDeviceAdd del driver client.

Il driver client specifica che sarà il proprietario dei criteri di risparmio energia per l'oggetto dispositivo. Come proprietario dei criteri di alimentazione, il driver client determina lo stato di alimentazione appropriato che il dispositivo deve immettere quando cambia lo stato di alimentazione del sistema. Il driver è anche responsabile dell'invio di richieste pertinenti al dispositivo per effettuare la transizione dello stato di alimentazione. Per impostazione predefinita, un driver client basato su UMDF non è il proprietario dei criteri di alimentazione; il framework gestisce tutte le transizioni di stato di alimentazione. Il framework invia automaticamente il dispositivo a D3 quando il sistema entra in uno stato di sospensione e viceversa riporta il dispositivo a D0 quando il sistema entra nello stato di lavoro di S0. Per altre informazioni, vedere Proprietà di Power Policy in UMDF.

Un'altra opzione di configurazione consiste nel specificare se il driver client è il driver di filtro o il driver di funzione per il dispositivo. Si noti che nell'esempio di codice il driver client non specifica in modo esplicito la preferenza. Ciò significa che il driver client è il driver di funzione e il framework deve creare un fdO nello stack di dispositivi. Se il driver client vuole essere il driver di filtro, il driver deve chiamare il metodo IWDFDeviceInitialize::SetFilter . In tal caso, il framework crea un FiDO nello stack di dispositivi.

Il driver client specifica inoltre che nessuna delle chiamate del framework ai callback del driver client viene sincronizzata. Il driver client gestisce tutte le attività di sincronizzazione. Per specificare tale preferenza, il driver client chiama il metodo IWDFDeviceInitialize::SetLockingConstraint .

Successivamente, il driver client ottiene un puntatore IUnknown alla relativa classe di callback del dispositivo chiamando IUnknown::QueryInterface. Successivamente, il driver client chiama IWDFDriver::CreateDevice, che crea l'oggetto dispositivo framework e registra il callback del dispositivo del driver client usando il puntatore IUnknown .

Si noti che il driver client archivia l'indirizzo dell'oggetto dispositivo (ricevuto tramite la chiamata IWDFDriver::CreateDevice ) in un membro dati privato della classe di callback del dispositivo e quindi rilascia tale riferimento chiamando DriverSafeRelease (funzione inline definita in Internal.h). Ciò è dovuto al fatto che la durata dell'oggetto dispositivo viene rilevata dal framework. Di conseguenza, il driver client non è necessario per mantenere il numero di riferimenti aggiuntivi dell'oggetto dispositivo.

Il codice modello definisce il metodo pubblico Configure, che registra il GUID dell'interfaccia del dispositivo e configura le code. L'esempio di codice seguente mostra la definizione del metodo Configure nella classe di callback del dispositivo, CMyDevice. La configurazione viene chiamata da IDriverEntry::OnDeviceAdd dopo la creazione dell'oggetto dispositivo 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;
}

Nell'esempio di codice precedente, il driver client esegue due attività principali: inizializzazione delle code per il flusso di I/O e registrazione del GUID dell'interfaccia del dispositivo.

Le code vengono create e configurate nella classe CMyIoQueue. La prima attività consiste nel creare un'istanza di tale classe chiamando il metodo statico denominato CreateInstanceAndInitialize. Il driver client chiama Configure per inizializzare le code. CreateInstanceAndInitialize e Configure vengono dichiarati in CMyIoQueue, descritto più avanti in questo argomento.

Il driver client chiama anche IWDFDevice::CreateDeviceInterface per registrare il GUID dell'interfaccia del dispositivo del driver client. Le applicazioni possono usare il GUID per inviare richieste al driver client. La costante GUID viene dichiarata in Internal.h.

Implementazione di IPnpCallbackHardware e attività specifiche di USB

Si esamini ora l'implementazione dell'interfaccia IPnpCallbackHardware in Device.cpp.

Ogni classe di callback del dispositivo deve implementare l'interfaccia IPnpCallbackHardware . Questa interfaccia include due metodi: IPnpCallbackHardware::OnPrepareHardware e IPnpCallbackHardware::OnReleaseHardware. Il framework chiama questi metodi in risposta a due eventi: quando PnP Manager avvia il dispositivo e quando rimuove il dispositivo. Quando un dispositivo viene avviato, viene stabilita la comunicazione con l'hardware, ma il dispositivo non ha immesso lo stato Di lavoro (D0). Pertanto, in IPnpCallbackHardware::OnPrepareHardware il driver client può ottenere informazioni sul dispositivo dall'hardware, allocare risorse e inizializzare gli oggetti framework necessari durante la durata del driver. Quando PnP Manager rimuove il dispositivo, il driver viene scaricato dal sistema. Il framework chiama l'implementazione IPnpCallbackHardware::OnReleaseHardware del driver client in cui il driver può rilasciare tali risorse e oggetti framework.

PnP Manager può generare altri tipi di eventi risultanti da modifiche dello stato PnP. Il framework fornisce la gestione predefinita per tali eventi. Il driver client può scegliere di partecipare alla gestione di tali eventi. Si consideri uno scenario in cui il dispositivo USB viene scollegato dall'host. PnP Manager riconosce l'evento e invia una notifica al framework. Se il driver client vuole eseguire attività aggiuntive in risposta all'evento, il driver deve implementare l'interfaccia IPnpCallback e il metodo IPnpCallback::OnSurpriseRemoval nella classe di callback del dispositivo. In caso contrario, il framework procede con la gestione predefinita dell'evento.

Un driver client USB deve recuperare informazioni sulle interfacce supportate, le impostazioni alternative e gli endpoint e configurarli prima di inviare richieste di I/O per il trasferimento dei dati. UMDF fornisce oggetti di destinazione I/O specializzati che semplificano molte attività di configurazione per il driver client. Per configurare un dispositivo USB, il driver client richiede informazioni sul dispositivo disponibili solo dopo l'avvio del dispositivo da parte di PnP Manager.

Questo codice modello crea tali oggetti nel metodo IPnpCallbackHardware::OnPrepareHardware .

In genere, il driver client esegue una o più di queste attività di configurazione (a seconda della progettazione del dispositivo):

  1. Recupera informazioni sulla configurazione corrente, ad esempio il numero di interfacce. Il framework seleziona la prima configurazione in un dispositivo USB. Il driver client non può selezionare un'altra configurazione nel caso di dispositivi con più configurazioni.
  2. Recupera informazioni sulle interfacce, ad esempio il numero di endpoint.
  3. Modifica l'impostazione alternativa all'interno di ogni interfaccia, se l'interfaccia supporta più di un'impostazione. Per impostazione predefinita, il framework seleziona la prima impostazione alternativa di ogni interfaccia nella prima configurazione in un dispositivo USB. Il driver client può scegliere di selezionare un'impostazione alternativa.
  4. Recupera informazioni sugli endpoint all'interno di ogni interfaccia.

Per eseguire queste attività, il driver client può usare questi tipi di oggetti di destinazione di I/O USB specializzati forniti da WDF.

Oggetto di destinazione I/O USB Descrizione Interfaccia UMDF
Oggetto dispositivo di destinazione Rappresenta un dispositivo USB e fornisce metodi per recuperare il descrittore del dispositivo e inviare richieste di controllo al dispositivo. IWDFUsbTargetDevice
Oggetto interfaccia di destinazione Rappresenta una singola interfaccia e fornisce metodi che un driver client può chiamare per selezionare un'impostazione alternativa e recuperare informazioni sull'impostazione. IWDFUsbInterface
Oggetto pipe di destinazione Rappresenta una singola pipe per un endpoint configurato nell'impostazione alternativa corrente per un'interfaccia. Il driver del bus USB seleziona ogni interfaccia nella configurazione selezionata e configura un canale di comunicazione per ogni endpoint all'interno dell'interfaccia. Nella terminologia USB, il canale di comunicazione viene chiamato pipe. IWDFUsbTargetPipe

L'esempio di codice seguente illustra l'implementazione di 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;
}

Per usare gli oggetti di destinazione I/O USB del framework, il driver client deve prima creare l'oggetto dispositivo di destinazione USB. Nel modello a oggetti del framework l'oggetto dispositivo di destinazione USB è un elemento figlio dell'oggetto dispositivo che rappresenta un dispositivo USB. L'oggetto dispositivo di destinazione USB viene implementato dal framework ed esegue tutte le attività a livello di dispositivo di un dispositivo USB, ad esempio la selezione di una configurazione.

Nell'esempio di codice precedente, il driver client esegue una query sull'oggetto dispositivo framework e ottiene un puntatore IWDFUsbTargetFactory alla class factory che crea l'oggetto dispositivo di destinazione USB. Usando tale puntatore, il driver client chiama il metodo IWDFUsbTargetDevice::CreateUsbTargetDevice . Il metodo crea l'oggetto dispositivo di destinazione USB e restituisce un puntatore all'interfaccia IWDFUsbTargetDevice . Il metodo seleziona anche la configurazione predefinita (prima) e l'impostazione alternativa 0 per ogni interfaccia in tale configurazione.

Il codice modello archivia l'indirizzo dell'oggetto dispositivo di destinazione USB (ricevuto tramite la chiamata IWDFDriver::CreateDevice ) in un membro dati privato della classe di callback del dispositivo e quindi rilascia tale riferimento chiamando DriverSafeRelease. Il conteggio dei riferimenti dell'oggetto dispositivo di destinazione USB viene gestito dal framework. L'oggetto è attivo fino a quando l'oggetto dispositivo è attivo. Il driver client deve rilasciare il riferimento in IPnpCallbackHardware::OnReleaseHardware.

Dopo che il driver client crea l'oggetto dispositivo di destinazione USB, il driver chiama i metodi IWDFUsbTargetDevice per eseguire queste attività:

  • Recuperare il dispositivo, la configurazione, i descrittori di interfaccia e altre informazioni, ad esempio la velocità del dispositivo.
  • Formattare e inviare richieste di controllo di I/O all'endpoint predefinito.
  • Impostare i criteri di alimentazione per l'intero dispositivo USB.

Per altre informazioni, vedere Uso di dispositivi USB in UMDF. L'esempio di codice seguente illustra l'implementazione di 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;
}

Codice sorgente della coda

L'oggetto coda del framework rappresenta la coda di I/O per un oggetto dispositivo framework specifico. Il codice sorgente completo per l'oggetto coda si trova in IoQueue.h e IoQueue.c.

IoQueue.h

Il file di intestazione IoQueue.h dichiara la classe di callback della coda.

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;

};

Nell'esempio di codice precedente, il driver client dichiara la classe di callback della coda. Quando viene creata un'istanza, l'oggetto viene associato all'oggetto coda del framework che gestisce la modalità di invio delle richieste al driver client. La classe definisce due metodi che creano e inizializzano l'oggetto coda del framework. Il metodo statico CreateInstanceAndInitialize crea un'istanza della classe di callback della coda e quindi chiama il metodo Initialize che crea e inizializza l'oggetto coda del framework. Specifica inoltre le opzioni di invio per l'oggetto coda.

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

Nell'esempio di codice seguente viene illustrata l'implementazione del metodo 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;
}

Nell'esempio di codice precedente, il driver client crea l'oggetto coda del framework. Il framework fornisce l'oggetto queue per gestire il flusso di richiesta al driver client.

Per creare l'oggetto, il driver client chiama IWDFDevice::CreateIoQueue nel riferimento IWDFDevice ottenuto in una chiamata precedente a IWDFDriver::CreateDevice.

Nella chiamata IWDFDevice::CreateIoQueue il driver client specifica determinate opzioni di configurazione prima che il framework crei code. Queste opzioni determinano se la coda è gestita dall'alimentazione, consente richieste di lunghezza zero e funge da coda predefinita per il driver. Il driver client fornisce questo set di informazioni:

  • Riferimento alla relativa classe di callback della coda

    Specifica un puntatore IUnknown alla relativa classe di callback della coda. In questo modo viene creata una relazione tra l'oggetto coda del framework e l'oggetto callback della coda del driver client. Quando gestione I/O riceve una nuova richiesta da un'applicazione, notifica il framework. Il framework usa quindi il puntatore IUnknown per richiamare i metodi pubblici esposti dall'oggetto callback della coda.

  • Coda predefinita o secondaria

    La coda deve essere la coda predefinita o una coda secondaria. Se l'oggetto coda del framework funge da coda predefinita, tutte le richieste vengono aggiunte alla coda. Una coda secondaria è dedicata a un tipo specifico di richiesta. Se il driver client richiede una coda secondaria, il driver deve anche chiamare il metodo IWDFDevice::ConfigureRequestDispatching per indicare il tipo di richiesta che il framework deve inserire nella coda specificata. Nel codice del modello il driver client passa FALSE nel parametro bDefaultQueue . Indica al metodo di creare una coda secondaria e non la coda predefinita. Successivamente chiama IWDFDevice::ConfigureRequestDispatching per indicare che la coda deve avere solo richieste di controllo di I/O del dispositivo (vedere il codice di esempio in questa sezione).

  • Tipo dispatch

    Il tipo di invio di un oggetto coda determina il modo in cui il framework recapita le richieste al driver client. Il meccanismo di recapito può essere sequenziale, in parallelo o da un meccanismo personalizzato definito dal driver client. Per una coda sequenziale, una richiesta non viene recapitata finché il driver client non completa la richiesta precedente. In modalità dispatch parallela, il framework inoltra le richieste non appena arrivano da I/O Manager. Ciò significa che il driver client può ricevere una richiesta durante l'elaborazione di un'altra. Nel meccanismo personalizzato, il client esegue manualmente il pull della richiesta successiva dall'oggetto coda del framework, quando il driver è pronto per elaborarlo. Nel codice del modello il driver client richiede una modalità di invio parallela.

  • Coda gestita dall'alimentazione

    L'oggetto coda del framework deve essere sincronizzato con il protocollo PnP e lo stato di alimentazione del dispositivo. Se il dispositivo non è in stato Working, l'oggetto coda del framework interrompe l'invio di tutte le richieste. Quando il dispositivo è in stato Di lavoro, l'oggetto coda riprende l'invio. In una coda gestita dall'alimentazione la sincronizzazione viene eseguita dal framework; in caso contrario, l'unità client deve gestire tale attività. Nel codice del modello il client richiede una coda gestita dall'alimentazione.

  • Richieste di lunghezza zero consentite

    Un driver client può indicare al framework di completare le richieste di I/O con buffer di lunghezza zero anziché inserirle nella coda. Nel codice del modello il client richiede al framework di completare tali richieste.

Un singolo oggetto coda del framework può gestire diversi tipi di richieste, ad esempio il controllo di lettura, scrittura e I/O del dispositivo e così via. Un driver client basato sul codice modello può elaborare solo le richieste di controllo di I/O del dispositivo. A tale scopo, la classe di callback della coda del driver client implementa l'interfaccia IQueueCallbackDeviceIoControl e il relativo metodo IQueueCallbackDeviceIoControl::OnDeviceIoControl . In questo modo il framework può richiamare l'implementazione del driver client di IQueueCallbackDeviceIoControl::OnDeviceIoControl quando il framework elabora una richiesta di controllo di I/O del dispositivo.

Per altri tipi di richieste, il driver client deve implementare l'interfaccia IQueueCallbackXxx corrispondente. Ad esempio, se il driver client vuole gestire le richieste di lettura, la classe di callback della coda deve implementare l'interfaccia IQueueCallbackRead e il relativo metodo IQueueCallbackRead::OnRead . Per informazioni sui tipi di richieste e sulle interfacce di callback, vedere Funzioni di callback degli eventi di coda I/O.

L'esempio di codice seguente mostra l'implementazione 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;

}

Verrà ora illustrato il funzionamento del meccanismo di accodamento. Per comunicare con il dispositivo USB, un'applicazione apre prima un handle al dispositivo e invia una richiesta di controllo di I/O del dispositivo chiamando la funzione DeviceIoControl con un codice di controllo specifico. A seconda del tipo di codice di controllo, l'applicazione può specificare buffer di input e output in tale chiamata. La chiamata viene infine ricevuta da I/O Manager, che notifica il framework. Il framework crea un oggetto richiesta framework e lo aggiunge all'oggetto coda del framework. Nel codice del modello, poiché l'oggetto queue è stato creato con il flag WdfIoQueueDispatchParallel, il callback viene richiamato non appena la richiesta viene aggiunta alla coda.

Quando il framework richiama il callback di eventi del driver client, passa un handle all'oggetto richiesta framework che contiene la richiesta (e i relativi buffer di input e output) inviati dall'applicazione. Inoltre, invia un handle all'oggetto coda del framework che contiene tale richiesta. Nel callback degli eventi, il driver client elabora la richiesta in base alle esigenze. Il codice del modello completa semplicemente la richiesta. Il driver client può eseguire attività più coinvolte. Ad esempio, se un'applicazione richiede determinate informazioni sul dispositivo, nel callback degli eventi il driver client può creare una richiesta di controllo USB e inviarla allo stack di driver USB per recuperare le informazioni sul dispositivo richieste. Le richieste di controllo USB sono descritte in Trasferimento di controllo USB.

Codice sorgente voce driver

Nel codice del modello, la voce del driver viene implementata in Dllsup.cpp.

Dllsup.cpp

Dopo la sezione include, viene dichiarata una costante GUID per il driver client. Tale GUID deve corrispondere al GUID nel file di installazione del driver (INF).

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

Il blocco di codice successivo dichiara la class factory per il driver client.

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

Il codice modello usa il supporto ATL per incapsulare codice COM complesso. La class factory eredita la classe modello CAtlDllModuleT che contiene tutto il codice necessario per la creazione del driver client.

Il frammento di codice seguente illustra l'implementazione di 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);
}

Se il driver client implementa la funzione DllMain , Windows considera DllMain come punto di ingresso per il modulo driver client. Windows chiama DllMain dopo il caricamento del modulo driver client in WUDFHost.exe. Windows chiama nuovamente DllMain subito prima che Windows scarica il driver client in memoria. DllMain può allocare e liberare variabili globali a livello di driver. Nel codice del modello il driver client inizializza e rilascia le risorse necessarie per la traccia WPP e richiama l'implementazione DllMain della classe ATL.

Il frammento di codice seguente illustra l'implementazione di DllGetClassObject.

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

Nel codice del modello la class factory e DllGetClassObject vengono implementate in ATL. Il frammento di codice precedente richiama semplicemente l'implementazione di ATL DllGetClassObject . In generale , DllGetClassObject deve eseguire le attività seguenti:

  1. Assicurarsi che il CLSID passato dal framework sia il GUID per il driver client. Il framework recupera il CLSID per il driver client dal file INF del driver. Durante la convalida, assicurarsi che il GUID specificato corrisponda a quello fornito in INF.
  2. Creare un'istanza della class factory implementata dal driver client. Nel codice del modello viene incapsulato dalla classe ATL.
  3. Ottenere un puntatore all'interfaccia IClassFactory della class factory e restituire il puntatore recuperato al framework.

Dopo il caricamento in memoria del modulo driver client, il framework chiama la funzione DllGetClassObject fornita dal driver. Nella chiamata del framework a DllGetClassObject, il framework passa il CLSID che identifica il driver client e richiede un puntatore all'interfaccia IClassFactory di una class factory. Il driver client implementa la class factory che facilita la creazione del callback del driver. Pertanto, il driver client deve contenere almeno una class factory. Il framework chiama quindi IClassFactory::CreateInstance e richiede un puntatore IDriverEntry alla classe di callback del driver.

Export.def

Affinché il framework chiami DllGetClassObject, il driver client deve esportare la funzione da un file con estensione def. Il file è già incluso nel progetto di Visual Studio.

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

Nel frammento di codice precedente di Export.def incluso nel progetto driver, il client fornisce il nome del modulo driver come LIBRARY e DllGetClassObject in EXPORTS. Per altre informazioni, vedere Esportazione da una DLL tramite file DEF.