Descripción de la estructura de código del controlador de cliente USB (UMDF)

En este tema, obtendrá información sobre el código fuente de un controlador de cliente USB basado en UMDF. Los ejemplos de código se generan mediante la plantilla controlador del modo de usuario USB incluida con Microsoft Visual Studio. El código de plantilla usa la Biblioteca de plantillas activas (ATL) para generar la infraestructura COM. AtL y detalles sobre la implementación COM en el controlador cliente no se describen aquí.

Para obtener instrucciones sobre cómo generar el código de plantilla de UMDF, consulte Cómo escribir el primer controlador de cliente USB (UMDF). El código de plantilla se describe en estas secciones:

Antes de analizar los detalles del código de plantilla, echemos un vistazo a algunas declaraciones en el archivo de encabezado (Internal.h) que son relevantes para el desarrollo de controladores UMDF.

Internal.h contiene estos archivos, incluidos en el Kit de controladores de Windows (WDK):

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

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

Atlbase.h y atlcom.h incluyen declaraciones para la compatibilidad con ATL. Cada clase implementada por el controlador cliente implementa la clase ATL public CComObjectRootEx.

Wudfddi.h siempre se incluye para el desarrollo de controladores UMDF. El archivo de encabezado incluye varias declaraciones y definiciones de métodos y estructuras que necesita para compilar un controlador UMDF.

Wudfusb.h incluye declaraciones y definiciones de estructuras y métodos UMDF necesarios para comunicarse con los objetos de destino de E/S USB proporcionados por el marco.

El siguiente bloque de Internal.h declara una constante GUID para la interfaz del dispositivo. Las aplicaciones pueden usar este GUID para abrir un identificador para el dispositivo mediante las API SetupDiXxx . El GUID se registra después de que el marco cree el objeto de dispositivo.

// 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 siguiente parte declara la macro de seguimiento y el GUID de seguimiento. Anote el GUID de seguimiento; Lo necesitará para habilitar el seguimiento.

#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 siguiente línea de Internal.h forward declara la clase implementada por el controlador de cliente para el objeto de devolución de llamada de cola. También incluye otros archivos de proyecto generados por la plantilla. Los archivos de encabezado de proyecto y implementación se describen más adelante en este tema.

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

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

Una vez instalado el controlador de cliente, Windows carga el controlador de cliente y el marco en una instancia del proceso de host. Desde aquí, el marco carga e inicializa el controlador de cliente. El marco realiza estas tareas:

  1. Crea un objeto de controlador en el marco de trabajo, que representa el controlador de cliente.
  2. Solicita un puntero de interfaz IDriverEntry desde el generador de clases.
  3. Crea un objeto de dispositivo en el marco de trabajo.
  4. Inicializa el objeto de dispositivo después de que el Administrador de PnP inicie el dispositivo.

Mientras el controlador se está cargando e inicializando, se producen varios eventos y el marco permite al controlador cliente participar en su control. En el lado del controlador cliente, el controlador realiza estas tareas:

  1. Implementa y exporta la función DllGetClassObject desde el módulo del controlador de cliente para que el marco pueda obtener una referencia al controlador.
  2. Proporciona una clase de devolución de llamada que implementa la interfaz IDriverEntry .
  3. Proporciona una clase de devolución de llamada que implementa interfaces IPnpCallbackXxx .
  4. Obtiene una referencia al objeto de dispositivo y la configura según los requisitos del controlador de cliente.

Código fuente de devolución de llamada del controlador

El marco crea el objeto de controlador, que representa la instancia del controlador cliente cargado por Windows. El controlador cliente proporciona al menos una devolución de llamada de controlador que registra el controlador con el marco de trabajo.

El código fuente completo de la devolución de llamada del controlador está en Driver.h y Driver.c.

El controlador cliente debe definir una clase de devolución de llamada de controlador que implemente interfaces IUnknown e IDriverEntry . El archivo de encabezado Driver.h declara una clase denominada CMyDriver, que define la devolución de llamada del controlador.

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)

La devolución de llamada del controlador debe ser una clase COM, lo que significa que debe implementar IUnknown y los métodos relacionados. En el código de plantilla, las clases ATL CComObjectRootEx y CComCoClass contienen los métodos IUnknown .

Una vez que Windows crea una instancia del proceso de host, el marco crea el objeto de controlador. Para ello, el marco crea una instancia de la clase de devolución de llamada del controlador y llama a la implementación de controladores de DllGetClassObject (que se describe en la sección Código fuente de entrada del controlador) y para obtener el puntero de interfaz IDriverEntry del controlador cliente. Esa llamada registra el objeto de devolución de llamada del controlador con el objeto del controlador de marco. Tras el registro correcto, el marco invoca la implementación del controlador cliente cuando se producen determinados eventos específicos del controlador. El primer método que invoca el marco es el método IDriverEntry::OnInitialize . En la implementación del controlador cliente de IDriverEntry::OnInitialize, el controlador cliente puede asignar recursos de controladores globales. Esos recursos deben liberarse en IDriverEntry::OnDeinitialize invocado por el marco justo antes de prepararse para descargar el controlador de cliente. El código de plantilla proporciona una implementación mínima para los métodos OnInitialize y OnDeinitialize .

El método más importante de IDriverEntry es IDriverEntry::OnDeviceAdd. Antes de que el marco cree el objeto de dispositivo del marco (descrito en la sección siguiente), llama a la implementación IDriverEntry::OnDeviceAdd del controlador. Al llamar al método , el marco pasa un puntero IWDFDriver al objeto de controlador y un puntero IWDFDeviceInitialize . El controlador cliente puede llamar a métodos IWDFDeviceInitialize para especificar determinadas opciones de configuración.

Normalmente, el controlador de cliente realiza las siguientes tareas en su implementación IDriverEntry::OnDeviceAdd :

  • Especifica información de configuración para el objeto de dispositivo que se va a crear.
  • Crea una instancia de la clase de devolución de llamada del dispositivo del controlador.
  • Crea el objeto de dispositivo de marco y registra su objeto de devolución de llamada de dispositivo con el marco de trabajo.
  • Inicializa el objeto de dispositivo de marco.
  • Registra el GUID de la interfaz de dispositivo del controlador cliente.

En el código de plantilla, IDriverEntry::OnDeviceAdd llama a un método estático, CMyDevice::CreateInstanceAndInitialize, definido en la clase de devolución de llamada del dispositivo. El método estático crea primero una instancia de la clase de devolución de llamada del controlador cliente y, a continuación, crea el objeto de dispositivo de marco. La clase de devolución de llamada del dispositivo también define un método público denominado Configure que realiza las tareas restantes mencionadas en la lista anterior. La implementación de la clase de devolución de llamada de dispositivo se describe en la sección siguiente. En el ejemplo de código siguiente se muestra la implementación IDriverEntry::OnDeviceAdd en el código de plantilla.

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

En el ejemplo de código siguiente se muestra la declaración de clase de dispositivo en 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
        );

};

Código fuente de devolución de llamada del dispositivo

El objeto de dispositivo de marco es una instancia de la clase framework que representa el objeto de dispositivo que se carga en la pila de dispositivos del controlador cliente. Para obtener información sobre la funcionalidad de un objeto de dispositivo, consulte Nodos de dispositivo y Pilas de dispositivos.

El código fuente completo del objeto de dispositivo se encuentra en Device.h y Device.c.

La clase de dispositivo de marco implementa la interfaz IWDFDevice . El controlador cliente es responsable de crear una instancia de esa clase en la implementación del controlador de IDriverEntry::OnDeviceAdd. Una vez creado el objeto, el controlador cliente obtiene un puntero IWDFDevice al nuevo objeto y llama a métodos en esa interfaz para administrar las operaciones del objeto de dispositivo.

Implementación de IDriverEntry::OnDeviceAdd

En la sección anterior, ha visto brevemente las tareas que realiza un controlador cliente en IDriverEntry::OnDeviceAdd. Esta es más información sobre esas tareas. El controlador de cliente:

  • Especifica información de configuración para el objeto de dispositivo que se va a crear.

    En la llamada de marco a la implementación del controlador cliente del método IDriverEntry::OnDeviceAdd , el marco pasa un puntero IWDFDeviceInitialize . El controlador cliente usa este puntero para especificar la información de configuración del objeto de dispositivo que se va a crear. Por ejemplo, el controlador cliente especifica si el controlador de cliente es un filtro o un controlador de función. Para identificar el controlador de cliente como controlador de filtro, llama a IWDFDeviceInitialize::SetFilter. En ese caso, el marco crea un objeto de dispositivo de filtro (FiDO); de lo contrario, se crea un objeto de dispositivo de función (FDO). Otra opción que puede establecer es el modo de sincronización llamando a IWDFDeviceInitialize::SetLockingConstraint.

  • Llama al método IWDFDriver::CreateDevice pasando el puntero de interfaz IWDFDeviceInitialize , una referencia IUnknown del objeto de devolución de llamada del dispositivo y una variable IWDFDevice de puntero a puntero.

    Si la llamada de IWDFDriver::CreateDevice es correcta:

    • El marco crea el objeto de dispositivo.

    • El marco registra la devolución de llamada del dispositivo con el marco.

      Una vez emparejada la devolución de llamada del dispositivo con el objeto de dispositivo framework, el marco y el controlador cliente controlan determinados eventos, como el estado PnP y los cambios de estado de energía. Por ejemplo, cuando el Administrador de PnP inicia el dispositivo, se notifica al marco. A continuación, el marco invoca la implementación IPnpCallbackHardware::OnPrepareHardware de la devolución de llamada del dispositivo. Cada controlador de cliente debe registrar al menos un objeto de devolución de llamada de dispositivo.

    • El controlador cliente recibe la dirección del nuevo objeto de dispositivo en la variable IWDFDevice . Al recibir un puntero al objeto de dispositivo de marco, el controlador cliente puede continuar con las tareas de inicialización, como configurar colas para el flujo de E/S y registrar el GUID de la interfaz de dispositivo.

  • Llama a IWDFDevice::CreateDeviceInterface para registrar el GUID de la interfaz de dispositivo del controlador cliente. Las aplicaciones pueden usar el GUID para enviar solicitudes al controlador cliente. La constante GUID se declara en Internal.h.

  • Inicializa colas para las transferencias de E/S hacia y desde el dispositivo.

El código de plantilla define el método auxiliar Initialize, que especifica la información de configuración y crea el objeto de dispositivo.

En el ejemplo de código siguiente se muestran las implementaciones de 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;
}

En el ejemplo de código anterior, el controlador cliente crea el objeto de dispositivo y registra su devolución de llamada de dispositivo. Antes de crear el objeto de dispositivo, el controlador especifica su preferencia de configuración llamando a métodos en el puntero de interfaz IWDFDeviceInitialize . Es el mismo puntero pasado por el marco en su llamada anterior al método IDriverEntry::OnDeviceAdd del controlador cliente.

El controlador cliente especifica que será el propietario de la directiva de energía para el objeto de dispositivo. Como propietario de la directiva de energía, el controlador cliente determina el estado de alimentación adecuado que el dispositivo debe especificar cuando cambia el estado de energía del sistema. El controlador también es responsable de enviar solicitudes pertinentes al dispositivo para realizar la transición del estado de energía. De forma predeterminada, un controlador de cliente basado en UMDF no es el propietario de la directiva de energía; el marco controla todas las transiciones de estado de energía. El marco envía automáticamente el dispositivo a D3 cuando el sistema entra en estado de suspensión y, por el contrario, devuelve el dispositivo a D0 cuando el sistema entra en estado de trabajo de S0. Para obtener más información, consulte Propiedad de Power Policy en UMDF.

Otra opción de configuración es especificar si el controlador de cliente es el controlador de filtro o el controlador de función del dispositivo. Observe que en el ejemplo de código, el controlador cliente no especifica explícitamente su preferencia. Esto significa que el controlador de cliente es el controlador de función y el marco debe crear un FDO en la pila de dispositivos. Si el controlador cliente quiere ser el controlador de filtro, el controlador debe llamar al método IWDFDeviceInitialize::SetFilter . En ese caso, el marco crea un FiDO en la pila de dispositivos.

El controlador cliente también especifica que no se sincroniza ninguna de las llamadas del marco a las devoluciones de llamada del controlador cliente. El controlador cliente controla todas las tareas de sincronización. Para especificar esa preferencia, el controlador cliente llama al método IWDFDeviceInitialize::SetLockingConstraint .

A continuación, el controlador cliente obtiene un puntero IUnknown a su clase de devolución de llamada de dispositivo mediante una llamada a IUnknown::QueryInterface. Posteriormente, el controlador cliente llama a IWDFDriver::CreateDevice, que crea el objeto de dispositivo de marco y registra la devolución de llamada del dispositivo del controlador cliente mediante el puntero IUnknown .

Observe que el controlador cliente almacena la dirección del objeto de dispositivo (recibido a través de la llamada IWDFDriver::CreateDevice ) en un miembro de datos privado de la clase de devolución de llamada del dispositivo y, a continuación, libera esa referencia mediante una llamada a DriverSafeRelease (función insertada definida en Internal.h). Esto se debe a que el marco realiza un seguimiento de la duración del objeto de dispositivo. Por lo tanto, el controlador de cliente no es necesario para mantener el recuento de referencias adicionales del objeto de dispositivo.

El código de plantilla define el método público Configure, que registra el GUID de la interfaz de dispositivo y configura colas. En el ejemplo de código siguiente se muestra la definición del método Configure en la clase de devolución de llamada del dispositivo, CMyDevice. IDriverEntry::OnDeviceAdd llama a la configuración después de crear el objeto de dispositivo de marco.

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

En el ejemplo de código anterior, el controlador cliente realiza dos tareas principales: inicializar colas para el flujo de E/S y registrar el GUID de la interfaz de dispositivo.

Las colas se crean y configuran en la clase CMyIoQueue. La primera tarea consiste en crear una instancia de esa clase mediante una llamada al método estático denominado CreateInstanceAndInitialize. El controlador cliente llama a Configurar para inicializar colas. CreateInstanceAndInitialize y Configure se declaran en CMyIoQueue, que se describe más adelante en este tema.

El controlador cliente también llama a IWDFDevice::CreateDeviceInterface para registrar el GUID de interfaz de dispositivo del controlador cliente. Las aplicaciones pueden usar el GUID para enviar solicitudes al controlador cliente. La constante GUID se declara en Internal.h.

Implementación de IPnpCallbackHardware y tareas específicas de USB

A continuación, echemos un vistazo a la implementación de la interfaz IPnpCallbackHardware en Device.cpp.

Cada clase de devolución de llamada de dispositivo debe implementar la interfaz IPnpCallbackHardware . Esta interfaz tiene dos métodos: IPnpCallbackHardware::OnPrepareHardware e IPnpCallbackHardware::OnReleaseHardware. El marco llama a esos métodos en respuesta a dos eventos: cuando el Administrador de PnP inicia el dispositivo y cuando quita el dispositivo. Cuando se inicia un dispositivo, se establece la comunicación con el hardware, pero el dispositivo no ha entrado en estado de trabajo (D0). Por lo tanto, en IPnpCallbackHardware::OnPrepareHardware , el controlador cliente puede obtener información del dispositivo del hardware, asignar recursos e inicializar objetos de marco necesarios durante la vigencia del controlador. Cuando el Administrador de PnP quita el dispositivo, el controlador se descarga del sistema. El marco llama a la implementación IPnpCallbackHardware::OnReleaseHardware del controlador cliente en la que el controlador puede liberar esos recursos y objetos de marco.

PnP Manager puede generar otros tipos de eventos resultantes de los cambios de estado de PnP. El marco proporciona control predeterminado para esos eventos. El controlador cliente puede optar por participar en el control de esos eventos. Considere un escenario en el que el dispositivo USB está desasociado del host. El Administrador de PnP reconoce ese evento y notifica al marco. Si el controlador cliente quiere realizar tareas adicionales en respuesta al evento, el controlador debe implementar la interfaz IPnpCallback y el método IPnpCallback::OnSurpriseRemoval relacionado en la clase de devolución de llamada del dispositivo. De lo contrario, el marco continúa con su control predeterminado del evento.

Un controlador cliente USB debe recuperar información sobre las interfaces admitidas, la configuración alternativa y los puntos de conexión y configurarlos antes de enviar solicitudes de E/S para la transferencia de datos. UMDF proporciona objetos de destino de E/S especializados que simplifican muchas de las tareas de configuración del controlador cliente. Para configurar un dispositivo USB, el controlador cliente requiere información del dispositivo que solo está disponible después de que el Administrador de PnP inicie el dispositivo.

Este código de plantilla crea esos objetos en el método IPnpCallbackHardware::OnPrepareHardware .

Normalmente, el controlador cliente realiza una o varias de estas tareas de configuración (según el diseño del dispositivo):

  1. Recupera información sobre la configuración actual, como el número de interfaces. El marco selecciona la primera configuración en un dispositivo USB. El controlador cliente no puede seleccionar otra configuración en el caso de dispositivos de varias configuraciones.
  2. Recupera información sobre las interfaces, como el número de puntos de conexión.
  3. Cambia la configuración alternativa dentro de cada interfaz, si la interfaz admite más de una configuración. De forma predeterminada, el marco selecciona la primera configuración alternativa de cada interfaz en la primera configuración de un dispositivo USB. El controlador cliente puede elegir seleccionar una configuración alternativa.
  4. Recupera información sobre los puntos de conexión dentro de cada interfaz.

Para realizar esas tareas, el controlador cliente puede usar estos tipos de objetos de destino de E/S USB especializados proporcionados por el WDF.

Objeto de destino de E/S USB Descripción Interfaz UMDF
Objeto de dispositivo de destino Representa un dispositivo USB y proporciona métodos para recuperar el descriptor de dispositivo y enviar solicitudes de control al dispositivo. IWDFUsbTargetDevice
Objeto de interfaz de destino Representa una interfaz individual y proporciona métodos a los que un controlador cliente puede llamar para seleccionar una configuración alternativa y recuperar información sobre la configuración. IWDFUsbInterface
Objeto de canalización de destino Representa una canalización individual para un punto de conexión que está configurado en la configuración alternativa actual de una interfaz. El controlador de bus USB selecciona cada interfaz de la configuración seleccionada y configura un canal de comunicación para cada punto de conexión dentro de la interfaz. En terminología USB, ese canal de comunicación se denomina canalización. IWDFUsbTargetPipe

En el ejemplo de código siguiente se muestra la implementación de 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;
}

Para usar los objetos de destino de E/S USB del marco, el controlador cliente debe crear primero el objeto de dispositivo de destino USB. En el modelo de objetos de marco, el objeto de dispositivo de destino USB es un elemento secundario del objeto de dispositivo que representa un dispositivo USB. El marco implementa el objeto de dispositivo de destino USB y realiza todas las tareas de nivel de dispositivo de un dispositivo USB, como seleccionar una configuración.

En el ejemplo de código anterior, el controlador cliente consulta el objeto de dispositivo de marco y obtiene un puntero IWDFUsbTargetFactory al generador de clases que crea el objeto de dispositivo de destino USB. Mediante ese puntero, el controlador cliente llama al método IWDFUsbTargetDevice::CreateUsbTargetDevice . El método crea el objeto de dispositivo de destino USB y devuelve un puntero a la interfaz IWDFUsbTargetDevice . El método también selecciona la configuración predeterminada (primero) y el valor alternativo 0 para cada interfaz de esa configuración.

El código de plantilla almacena la dirección del objeto de dispositivo de destino USB (recibido a través de la llamada IWDFDriver::CreateDevice ) en un miembro de datos privado de la clase de devolución de llamada del dispositivo y, a continuación, libera esa referencia mediante una llamada a DriverSafeRelease. El marco mantiene el recuento de referencias del objeto de dispositivo de destino USB. El objeto está activo siempre que el objeto de dispositivo esté activo. El controlador cliente debe liberar la referencia en IPnpCallbackHardware::OnReleaseHardware.

Una vez que el controlador cliente crea el objeto de dispositivo de destino USB, el controlador llama a los métodos IWDFUsbTargetDevice para realizar estas tareas:

  • Recupere el dispositivo, la configuración, los descriptores de interfaz y otra información, como la velocidad del dispositivo.
  • Dar formato y enviar solicitudes de control de E/S al punto de conexión predeterminado.
  • Establezca la directiva de alimentación para todo el dispositivo USB.

Para obtener más información, vea Trabajar con dispositivos USB en UMDF. En el ejemplo de código siguiente se muestra la implementación 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;
}

Código fuente de cola

El objeto de cola de marco representa la cola de E/S de un objeto de dispositivo de marco específico. El código fuente completo del objeto queue está en IoQueue.h e IoQueue.c.

IoQueue.h

El archivo de encabezado IoQueue.h declara la clase de devolución de llamada de cola.

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;

};

En el ejemplo de código anterior, el controlador cliente declara la clase de devolución de llamada de cola. Cuando se crea una instancia, el objeto se asocia con el objeto de cola de marco que controla la forma en que se envían las solicitudes al controlador cliente. La clase define dos métodos que crean e inicializan el objeto de cola del marco. El método estático CreateInstanceAndInitialize crea una instancia de la clase de devolución de llamada de cola y, a continuación, llama al método Initialize que crea e inicializa el objeto de cola de marco. También especifica las opciones de distribución para el objeto de cola.

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

En el ejemplo de código siguiente se muestra la implementación del método 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;
}

En el ejemplo de código anterior, el controlador cliente crea el objeto de cola del marco. El marco proporciona el objeto queue para controlar el flujo de solicitud al controlador de cliente.

Para crear el objeto, el controlador cliente llama a IWDFDevice::CreateIoQueue en la referencia de IWDFDevice obtenida en una llamada anterior a IWDFDriver::CreateDevice.

En la llamada IWDFDevice::CreateIoQueue , el controlador cliente especifica determinadas opciones de configuración antes de que el marco cree colas. Estas opciones determinan si la cola está administrada por energía, permite solicitudes de longitud cero y actúa como la cola predeterminada para el controlador. El controlador cliente proporciona este conjunto de información:

  • Referencia a su clase de devolución de llamada de cola

    Especifica un puntero IUnknown a su clase de devolución de llamada de cola. Esto crea una asociación entre el objeto de cola de marco y el objeto de devolución de llamada de cola del controlador cliente. Cuando el Administrador de E/S recibe una nueva solicitud de una aplicación, notifica al marco. A continuación, el marco usa el puntero IUnknown para invocar los métodos públicos expuestos por el objeto de devolución de llamada de cola.

  • Cola predeterminada o secundaria

    La cola debe ser la cola predeterminada o una cola secundaria. Si el objeto de cola de marco actúa como la cola predeterminada, todas las solicitudes se agregan a la cola. Una cola secundaria se dedica a un tipo específico de solicitud. Si el controlador cliente solicita una cola secundaria, el controlador también debe llamar al método IWDFDevice::ConfigureRequestDispatching para indicar el tipo de solicitud que el marco debe colocar en la cola especificada. En el código de plantilla, el controlador de cliente pasa FALSE en el parámetro bDefaultQueue . Esto indica al método que cree una cola secundaria y no la cola predeterminada. Más adelante llama a IWDFDevice::ConfigureRequestDispatching para indicar que la cola solo debe tener solicitudes de control de E/S del dispositivo (consulte el código de ejemplo de esta sección).

  • Tipo de envío

    El tipo de envío de un objeto de cola determina cómo el marco entrega solicitudes al controlador de cliente. El mecanismo de entrega puede ser secuencial, en paralelo o mediante un mecanismo personalizado definido por el controlador de cliente. Para una cola secuencial, una solicitud no se entrega hasta que el controlador cliente completa la solicitud anterior. En modo de envío paralelo, el marco reenvía las solicitudes en cuanto llegan desde el Administrador de E/S. Esto significa que el controlador cliente puede recibir una solicitud mientras se procesa otra. En el mecanismo personalizado, el cliente extrae manualmente la siguiente solicitud del objeto de cola del marco, cuando el controlador está listo para procesarlo. En el código de plantilla, el controlador cliente solicita un modo de envío paralelo.

  • Cola administrada por Power

    El objeto de cola de marco debe sincronizarse con el PnP y el estado de alimentación del dispositivo. Si el dispositivo no está en estado De trabajo, el objeto de cola del marco deja de enviar todas las solicitudes. Cuando el dispositivo está en estado De trabajo, el objeto de cola reanuda la distribución. En una cola administrada por energía, el marco realiza la sincronización; de lo contrario, la unidad de cliente debe controlar esa tarea. En el código de plantilla, el cliente solicita una cola administrada por energía.

  • Solicitudes de longitud cero permitidas

    Un controlador cliente puede indicar al marco que complete las solicitudes de E/S con búferes de longitud cero en lugar de colocarlas en la cola. En el código de plantilla, el cliente solicita al marco que complete dichas solicitudes.

Un único objeto de cola de marco puede controlar varios tipos de solicitudes, como el control de lectura, escritura y E/S del dispositivo, etc. Un controlador cliente basado en el código de plantilla solo puede procesar solicitudes de control de E/S del dispositivo. Para ello, la clase de devolución de llamada de la cola del controlador cliente implementa la interfaz IQueueCallbackDeviceIoControl y su método IQueueCallbackDeviceIoControl::OnDeviceIoControl . Esto permite al marco invocar la implementación del controlador cliente de IQueueCallbackDeviceIoControl::OnDeviceIoControl cuando el marco procesa una solicitud de control de E/S de dispositivo.

Para otros tipos de solicitudes, el controlador cliente debe implementar la interfaz IQueueCallbackXxx correspondiente. Por ejemplo, si el controlador cliente quiere controlar las solicitudes de lectura, la clase de devolución de llamada de cola debe implementar la interfaz IQueueCallbackRead y su método IQueueCallbackRead::OnRead . Para obtener información sobre los tipos de solicitudes e interfaces de devolución de llamada, vea Funciones de devolución de llamada de eventos de cola de E /S.

En el ejemplo de código siguiente se muestra la implementación 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;

}

Veamos cómo funciona el mecanismo de cola. Para comunicarse con el dispositivo USB, una aplicación abre primero un identificador para el dispositivo y envía una solicitud de control de E/S de dispositivo mediante una llamada a la función DeviceIoControl con un código de control específico. Según el tipo de código de control, la aplicación puede especificar búferes de entrada y salida en esa llamada. Finalmente, el Administrador de E/S recibe la llamada, que notifica al marco. El marco crea un objeto de solicitud de marco y lo agrega al objeto de cola del marco. En el código de plantilla, dado que el objeto queue se creó con la marca WdfIoQueueDispatchParallel, la devolución de llamada se invoca en cuanto se agrega la solicitud a la cola.

Cuando el marco invoca la devolución de llamada de eventos del controlador cliente, pasa un identificador al objeto de solicitud del marco que contiene la solicitud (y sus búferes de entrada y salida) enviadas por la aplicación. Además, envía un identificador al objeto de cola del marco que contiene esa solicitud. En la devolución de llamada de evento, el controlador cliente procesa la solicitud según sea necesario. El código de plantilla simplemente completa la solicitud. El controlador cliente puede realizar tareas más implicadas. Por ejemplo, si una aplicación solicita cierta información del dispositivo, en la devolución de llamada del evento, el controlador cliente puede crear una solicitud de control USB y enviarla a la pila del controlador USB para recuperar la información del dispositivo solicitada. Las solicitudes de control USB se describen en Transferencia de control USB.

Código fuente de entrada del controlador

En el código de plantilla, la entrada del controlador se implementa en Dllsup.cpp.

Dllsup.cpp

Después de la sección include, se declara una constante GUID para el controlador cliente. Ese GUID debe coincidir con el GUID en el archivo de instalación del controlador (INF).

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

El siguiente bloque de código declara el generador de clases para el controlador cliente.

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

El código de plantilla usa compatibilidad con ATL para encapsular código COM complejo. El generador de clases hereda la clase de plantilla CAtlDllModuleT que contiene todo el código necesario para crear el controlador cliente.

En el fragmento de código siguiente se muestra la implementación 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 el controlador cliente implementa la función DllMain , Windows considera que DllMain es el punto de entrada del módulo del controlador cliente. Windows llama a DllMain después de cargar el módulo del controlador cliente en WUDFHost.exe. Windows llama de nuevo a DllMain justo antes de que Windows descargue el controlador cliente en memoria. DllMain puede asignar y liberar variables globales en el nivel de controlador. En el código de plantilla, el controlador de cliente inicializa y libera los recursos necesarios para el seguimiento de WPP e invoca la implementación dllMain de la clase ATL.

En el fragmento de código siguiente se muestra la implementación de DllGetClassObject.

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

En el código de plantilla, el generador de clases y DllGetClassObject se implementan en ATL. El fragmento de código anterior simplemente invoca la implementación DLLGetClassObject de ATL. En general, DllGetClassObject debe realizar las tareas siguientes:

  1. Asegúrese de que el CLSID pasado por el marco es el GUID del controlador de cliente. El marco recupera el CLSID para el controlador cliente del archivo INF del controlador. Al validar, asegúrese de que el GUID especificado coincide con el que proporcionó en inf.
  2. Cree una instancia del generador de clases implementado por el controlador cliente. En el código de plantilla, esta clase está encapsulada por la clase ATL.
  3. Obtenga un puntero a la interfaz IClassFactory del generador de clases y devuelva el puntero recuperado al marco.

Una vez cargado el módulo del controlador cliente en la memoria, el marco llama a la función DllGetClassObject proporcionada por el controlador. En la llamada del marco a DllGetClassObject, el marco pasa el CLSID que identifica el controlador cliente y solicita un puntero a la interfaz IClassFactory de un generador de clases. El controlador cliente implementa el generador de clases que facilita la creación de la devolución de llamada del controlador. Por lo tanto, el controlador de cliente debe contener al menos un generador de clases. A continuación, el marco llama a IClassFactory::CreateInstance y solicita un puntero IDriverEntry a la clase de devolución de llamada del controlador.

Exports.def

Para que el marco llame a DllGetClassObject, el controlador cliente debe exportar la función desde un archivo .def. El archivo ya está incluido en el proyecto de Visual Studio.

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

En el fragmento de código anterior de Export.def incluido con el proyecto de controlador, el cliente proporciona el nombre del módulo de controlador como LIBRARY y DllGetClassObject en EXPORTS. Para obtener más información, vea Exportar desde un archivo DLL mediante archivos DEF.