Partilhar via


Noções básicas sobre a estrutura de código do driver de cliente USB (UMDF)

Neste tópico, você aprenderá sobre o código-fonte de um driver de cliente USB baseado em UMDF. Os exemplos de código são gerados pelo modelo driver de modo de usuário USB incluído no Microsoft Visual Studio. O código de modelo usa a ATL (Biblioteca de Modelos Ativos) para gerar a infraestrutura COM. A ATL e os detalhes sobre a implementação de COM no driver do cliente não são discutidos aqui.

Para obter instruções sobre como gerar o código de modelo UMDF, consulte Como escrever seu primeiro driver de cliente USB (UMDF). O código do modelo é discutido nestas seções:

Antes de discutir os detalhes do código do modelo, vamos examinar algumas declarações no arquivo de cabeçalho (Internal.h) relevantes para o desenvolvimento de driver UMDF.

Internal.h contém esses arquivos, incluídos no WDK (Kit de Driver do Windows):

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

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

Atlbase.h e atlcom.h incluem declarações para suporte à ATL. Cada classe implementada pelo driver cliente implementa a classe ATL pública CComObjectRootEx.

Wudfddi.h é sempre incluído para o desenvolvimento de driver UMDF. O arquivo de cabeçalho inclui várias declarações e definições de métodos e estruturas que você precisa para compilar um driver UMDF.

Wudfusb.h inclui declarações e definições de estruturas e métodos UMDF necessários para se comunicar com os objetos de destino de E/S USB fornecidos pela estrutura.

O próximo bloco em Internal.h declara uma constante GUID para a interface do dispositivo. Os aplicativos podem usar esse GUID para abrir um identificador para o dispositivo usando APIs SetupDiXxx . O GUID é registrado depois que a estrutura cria o 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);

A próxima parte declara a macro de rastreamento e o GUID de rastreamento. Observe o GUID de rastreamento; você precisará dele para habilitar o rastreamento.

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

A próxima linha em Internal.h forward declara a classe implementada pelo driver do cliente para o objeto de retorno de chamada de fila. Ele também inclui outros arquivos de projeto gerados pelo modelo. Os arquivos de implementação e cabeçalho do projeto são discutidos posteriormente neste tópico.

// Forward definition of queue.

typedef class CMyIoQueue *PCMyIoQueue;

// Include the type specific headers.

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

Depois que o driver cliente é instalado, o Windows carrega o driver cliente e a estrutura em uma instância do processo de host. A partir daqui, a estrutura carrega e inicializa o driver cliente. A estrutura executa estas tarefas:

  1. Cria um objeto de driver na estrutura , que representa o driver do cliente.
  2. Solicita um ponteiro de interface IDriverEntry da fábrica de classes.
  3. Cria um objeto de dispositivo na estrutura.
  4. Inicializa o objeto do dispositivo depois que o Gerenciador de PnP inicia o dispositivo.

Enquanto o driver está carregando e inicializando, vários eventos ocorrem e a estrutura permite que o driver cliente participe na manipulação deles. No lado do driver do cliente, o driver executa estas tarefas:

  1. Implementa e exporta a função DllGetClassObject do módulo de driver do cliente para que a estrutura possa obter uma referência ao driver.
  2. Fornece uma classe de retorno de chamada que implementa a interface IDriverEntry .
  3. Fornece uma classe de retorno de chamada que implementa interfaces IPnpCallbackXxx .
  4. Obtém uma referência ao objeto de dispositivo e o configura de acordo com os requisitos do driver do cliente.

Código-fonte de retorno de chamada do driver

A estrutura cria o objeto driver, que representa a instância do driver cliente carregado pelo Windows. O driver cliente fornece pelo menos um retorno de chamada de driver que registra o driver com a estrutura .

O código-fonte completo para o retorno de chamada do driver está em Driver.h e Driver.c.

O driver cliente deve definir uma classe de retorno de chamada de driver que implementa interfaces IUnknown e IDriverEntry . O arquivo de cabeçalho, Driver.h, declara uma classe chamada CMyDriver, que define o retorno de chamada do 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)

O retorno de chamada do driver deve ser uma classe COM, o que significa que ele deve implementar IUnknown e os métodos relacionados. No código do modelo, as classes ATL CComObjectRootEx e CComCoClass contêm os métodos IUnknown .

Depois que o Windows instancia o processo de host, a estrutura cria o objeto de driver. Para fazer isso, a estrutura cria uma instância da classe de retorno de chamada de driver e chama a implementação de drivers de DllGetClassObject (discutida na seção Código-fonte de entrada do driver ) e para obter o ponteiro da interface IDriverEntry do driver cliente. Essa chamada registra o objeto de retorno de chamada do driver com o objeto de driver de estrutura. Após o registro bem-sucedido, a estrutura invoca a implementação do driver cliente quando determinados eventos específicos do driver ocorrem. O primeiro método invocado pela estrutura é o método IDriverEntry::OnInitialize . Na implementação do driver cliente de IDriverEntry::OnInitialize, o driver cliente pode alocar recursos globais de driver. Esses recursos devem ser lançados em IDriverEntry::OnDeinitialize que é invocado pela estrutura pouco antes de se preparar para descarregar o driver cliente. O código de modelo fornece implementação mínima para os métodos OnInitialize e OnDeinitialize .

O método mais importante de IDriverEntry é IDriverEntry::OnDeviceAdd. Antes que a estrutura crie o objeto de dispositivo de estrutura (discutido na próxima seção), ele chama a implementação IDriverEntry::OnDeviceAdd do driver. Ao chamar o método , a estrutura passa um ponteiro IWDFDriver para o objeto driver e um ponteiro IWDFDeviceInitialize . O driver cliente pode chamar métodos IWDFDeviceInitialize para especificar determinadas opções de configuração.

Normalmente, o driver cliente executa as seguintes tarefas em sua implementação IDriverEntry::OnDeviceAdd :

  • Especifica informações de configuração para o objeto de dispositivo a ser criado.
  • Cria uma instância da classe de retorno de chamada do dispositivo do driver.
  • Cria o objeto de dispositivo de estrutura e registra seu objeto de retorno de chamada de dispositivo com a estrutura .
  • Inicializa o objeto de dispositivo de estrutura.
  • Registra o GUID da interface do dispositivo do driver cliente.

No código do modelo, IDriverEntry::OnDeviceAdd chama um método estático, CMyDevice::CreateInstanceAndInitialize, definido na classe de retorno de chamada do dispositivo. O método estático primeiro cria uma instância da classe de retorno de chamada do dispositivo do driver cliente e, em seguida, cria o objeto de dispositivo de estrutura. A classe de retorno de chamada do dispositivo também define um método público chamado Configurar que executa as tarefas restantes mencionadas na lista anterior. A implementação da classe de retorno de chamada do dispositivo é discutida na próxima seção. O exemplo de código a seguir mostra a implementação IDriverEntry::OnDeviceAdd no código do modelo.

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

O exemplo de código a seguir mostra a declaração de classe de dispositivo em 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-fonte de retorno de chamada do dispositivo

O objeto de dispositivo de estrutura é uma instância da classe de estrutura que representa o objeto de dispositivo carregado na pilha de dispositivos do driver do cliente. Para obter informações sobre a funcionalidade de um objeto de dispositivo, consulte Nós de dispositivo e pilhas de dispositivos.

O código-fonte completo do objeto de dispositivo está localizado em Device.h e Device.c.

A classe de dispositivo framework implementa a interface IWDFDevice . O driver cliente é responsável por criar uma instância dessa classe na implementação do driver de IDriverEntry::OnDeviceAdd. Depois que o objeto é criado, o driver cliente obtém um ponteiro IWDFDevice para o novo objeto e chama métodos nessa interface para gerenciar as operações do objeto de dispositivo.

Implementação de IDriverEntry::OnDeviceAdd

Na seção anterior, você viu brevemente as tarefas que um driver cliente executa em IDriverEntry::OnDeviceAdd. Veja mais informações sobre essas tarefas. O driver do cliente:

  • Especifica informações de configuração para o objeto de dispositivo a ser criado.

    Na chamada de estrutura para a implementação do driver cliente do método IDriverEntry::OnDeviceAdd , a estrutura passa um ponteiro IWDFDeviceInitialize . O driver cliente usa esse ponteiro para especificar informações de configuração para o objeto de dispositivo a ser criado. Por exemplo, o driver cliente especifica se o driver do cliente é um filtro ou um driver de função. Para identificar o driver do cliente como um driver de filtro, ele chama IWDFDeviceInitialize::SetFilter. Nesse caso, a estrutura cria um fiDO (objeto de dispositivo de filtro); caso contrário, um FDO (objeto de dispositivo de função) será criado. Outra opção que você pode definir é o modo de sincronização chamando IWDFDeviceInitialize::SetLockingConstraint.

  • Chama o método IWDFDriver::CreateDevice passando o ponteiro da interface IWDFDeviceInitialize , uma referência IUnknown do objeto de retorno de chamada do dispositivo e uma variável IWDFDevice ponteiro a ponteiro.

    Se a chamada IWDFDriver::CreateDevice for bem-sucedida:

    • A estrutura cria o objeto do dispositivo.

    • A estrutura registra o retorno de chamada do dispositivo com a estrutura .

      Depois que o retorno de chamada do dispositivo é emparelhado com o objeto de dispositivo de estrutura, a estrutura e o driver cliente lidam com determinados eventos, como o estado PnP e as alterações de estado de energia. Por exemplo, quando o Gerenciador PnP inicia o dispositivo, a estrutura é notificada. Em seguida, a estrutura invoca a implementação IPnpCallbackHardware::OnPrepareHardware do retorno de chamada do dispositivo. Cada driver de cliente deve registrar pelo menos um objeto de retorno de chamada de dispositivo.

    • O driver cliente recebe o endereço do novo objeto de dispositivo na variável IWDFDevice . Ao receber um ponteiro para o objeto de dispositivo de estrutura, o driver do cliente pode prosseguir com tarefas de inicialização, como configurar filas para o fluxo de E/S e registrar o GUID da interface do dispositivo.

  • Chama IWDFDevice::CreateDeviceInterface para registrar o GUID da interface do dispositivo do driver cliente. Os aplicativos podem usar o GUID para enviar solicitações ao driver cliente. A constante GUID é declarada em Internal.h.

  • Inicializa filas para transferências de E/S de e para o dispositivo.

O código de modelo define o método auxiliar Initialize, que especifica informações de configuração e cria o objeto de dispositivo.

O exemplo de código a seguir mostra implementações para Inicializar.

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

No exemplo de código anterior, o driver do cliente cria o objeto de dispositivo e registra o retorno de chamada do dispositivo. Antes de criar o objeto de dispositivo, o driver especifica sua preferência de configuração chamando métodos no ponteiro da interface IWDFDeviceInitialize . Esse é o mesmo ponteiro passado pela estrutura em sua chamada anterior para o método IDriverEntry::OnDeviceAdd do driver cliente.

O driver do cliente especifica que ele será o proprietário da política de energia para o objeto do dispositivo. Como proprietário da política de energia, o driver do cliente determina o estado de energia apropriado que o dispositivo deve inserir quando o estado de energia do sistema é alterado. O driver também é responsável por enviar solicitações relevantes para o dispositivo para fazer a transição do estado de energia. Por padrão, um driver de cliente baseado em UMDF não é o proprietário da política de energia; a estrutura lida com todas as transições de estado de energia. A estrutura envia automaticamente o dispositivo para D3 quando o sistema entra em um estado de suspensão e, por outro lado, retorna o dispositivo para D0 quando o sistema entra no estado de trabalho de S0. Para obter mais informações, consulte Propriedade da Política de Energia no UMDF.

Outra opção de configuração é especificar se o driver do cliente é o driver de filtro ou o driver de função para o dispositivo. Observe que, no exemplo de código, o driver cliente não especifica explicitamente sua preferência. Isso significa que o driver do cliente é o driver de função e a estrutura deve criar um FDO na pilha do dispositivo. Se o driver cliente quiser ser o driver de filtro, o driver deverá chamar o método IWDFDeviceInitialize::SetFilter . Nesse caso, a estrutura cria um FiDO na pilha de dispositivos.

O driver do cliente também especifica que nenhuma das chamadas da estrutura para os retornos de chamada do driver cliente são sincronizadas. O driver do cliente lida com todas as tarefas de sincronização. Para especificar essa preferência, o driver cliente chama o método IWDFDeviceInitialize::SetLockingConstraint .

Em seguida, o driver cliente obtém um ponteiro IUnknown para sua classe de retorno de chamada de dispositivo chamando IUnknown::QueryInterface. Posteriormente, o driver cliente chama IWDFDriver::CreateDevice, que cria o objeto de dispositivo de estrutura e registra o retorno de chamada do dispositivo do driver cliente usando o ponteiro IUnknown .

Observe que o driver cliente armazena o endereço do objeto de dispositivo (recebido por meio da chamada IWDFDriver::CreateDevice ) em um membro de dados privados da classe de retorno de chamada do dispositivo e libera essa referência chamando DriverSafeRelease (função embutida definida em Internal.h). Isso ocorre porque o tempo de vida do objeto de dispositivo é acompanhado pela estrutura. Portanto, o driver cliente não é necessário para manter a contagem de referência adicional do objeto do dispositivo.

O código de modelo define o método público Configure, que registra o GUID da interface do dispositivo e configura filas. O exemplo de código a seguir mostra a definição do método Configure na classe de retorno de chamada do dispositivo, CMyDevice. Configure é chamado por IDriverEntry::OnDeviceAdd depois que o objeto de dispositivo de estrutura é criado.

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

No exemplo de código anterior, o driver do cliente executa duas tarefas main: inicializar filas para fluxo de E/S e registrar o GUID da interface do dispositivo.

As filas são criadas e configuradas na classe CMyIoQueue. A primeira tarefa é instanciar essa classe chamando o método estático chamado CreateInstanceAndInitialize. O driver cliente chama Configurar para inicializar filas. CreateInstanceAndInitialize e Configure são declarados em CMyIoQueue, que é discutido posteriormente neste tópico.

O driver cliente também chama IWDFDevice::CreateDeviceInterface para registrar o GUID da interface do dispositivo do driver cliente. Os aplicativos podem usar o GUID para enviar solicitações ao driver cliente. A constante GUID é declarada em Internal.h.

Implementação de IPnpCallbackHardware e tarefas específicas de USB

Em seguida, vamos examinar a implementação da interface IPnpCallbackHardware em Device.cpp.

Cada classe de retorno de chamada de dispositivo deve implementar a interface IPnpCallbackHardware . Essa interface tem dois métodos: IPnpCallbackHardware::OnPrepareHardware e IPnpCallbackHardware::OnReleaseHardware. A estrutura chama esses métodos em resposta a dois eventos: quando o Gerenciador de PnP inicia o dispositivo e quando remove o dispositivo. Quando um dispositivo é iniciado, a comunicação com o hardware é estabelecida, mas o dispositivo não entrou em Estado de trabalho (D0). Portanto, em IPnpCallbackHardware::OnPrepareHardware , o driver cliente pode obter informações do dispositivo do hardware, alocar recursos e inicializar objetos de estrutura necessários durante o tempo de vida do driver. Quando o Gerenciador PnP remove o dispositivo, o driver é descarregado do sistema. A estrutura chama a implementação IPnpCallbackHardware::OnReleaseHardware do driver do cliente na qual o driver pode liberar esses recursos e objetos de estrutura.

O Gerenciador de PnP pode gerar outros tipos de eventos resultantes de alterações de estado PnP. A estrutura fornece tratamento padrão para esses eventos. O driver cliente pode optar por participar da manipulação desses eventos. Considere um cenário em que o dispositivo USB é desanexado do host. O Gerenciador PnP reconhece esse evento e notifica a estrutura. Se o driver cliente quiser executar tarefas adicionais em resposta ao evento, o driver deverá implementar a interface IPnpCallback e o método IPnpCallback::OnSurpriseRemoval relacionado na classe de retorno de chamada do dispositivo. Caso contrário, a estrutura continuará com seu tratamento padrão do evento.

Um driver de cliente USB deve recuperar informações sobre as interfaces com suporte, configurações alternativas e pontos de extremidade e configurá-los antes de enviar solicitações de E/S para transferência de dados. O UMDF fornece objetos de destino de E/S especializados que simplificam muitas das tarefas de configuração para o driver cliente. Para configurar um dispositivo USB, o driver do cliente requer informações do dispositivo que estão disponíveis somente depois que o Gerenciador PnP inicia o dispositivo.

Esse código de modelo cria esses objetos no método IPnpCallbackHardware::OnPrepareHardware .

Normalmente, o driver cliente executa uma ou mais dessas tarefas de configuração (dependendo do design do dispositivo):

  1. Recupera informações sobre a configuração atual, como o número de interfaces. A estrutura seleciona a primeira configuração em um dispositivo USB. O driver do cliente não pode selecionar outra configuração no caso de dispositivos de várias configurações.
  2. Recupera informações sobre interfaces, como o número de pontos de extremidade.
  3. Altera a configuração alternativa em cada interface, se a interface der suporte a mais de uma configuração. Por padrão, a estrutura seleciona a primeira configuração alternativa de cada interface na primeira configuração em um dispositivo USB. O driver cliente pode optar por selecionar uma configuração alternativa.
  4. Recupera informações sobre pontos de extremidade em cada interface.

Para executar essas tarefas, o driver cliente pode usar esses tipos de objetos de destino de E/S USB especializados fornecidos pelo WDF.

Objeto de destino de E/S USB Descrição Interface UMDF
Objeto de dispositivo de destino Representa um dispositivo USB e fornece métodos para recuperar o descritor do dispositivo e enviar solicitações de controle para o dispositivo. IWDFUsbTargetDevice
Objeto de interface de destino Representa uma interface individual e fornece métodos que um driver de cliente pode chamar para selecionar uma configuração alternativa e recuperar informações sobre a configuração. IWDFUsbInterface
Objeto pipe de destino Representa um pipe individual para um ponto de extremidade que está configurado na configuração alternativa atual para uma interface. O driver de barramento USB seleciona cada interface na configuração selecionada e configura um canal de comunicação para cada ponto de extremidade dentro da interface. Na terminologia USB, esse canal de comunicação é chamado de pipe. IWDFUsbTargetPipe

O exemplo de código a seguir mostra a implementação 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 os objetos de destino de E/S USB da estrutura, o driver cliente deve primeiro criar o objeto de dispositivo de destino USB. No modelo de objeto da estrutura, o objeto de dispositivo de destino USB é um filho do objeto de dispositivo que representa um dispositivo USB. O objeto de dispositivo de destino USB é implementado pela estrutura e executa todas as tarefas no nível do dispositivo de um dispositivo USB, como selecionar uma configuração.

No exemplo de código anterior, o driver de cliente consulta o objeto de dispositivo da estrutura e obtém um ponteiro IWDFUsbTargetFactory para a fábrica de classes que cria o objeto de dispositivo de destino USB. Usando esse ponteiro, o driver cliente chama o método IWDFUsbTargetDevice::CreateUsbTargetDevice . O método cria o objeto de dispositivo de destino USB e retorna um ponteiro para a interface IWDFUsbTargetDevice . O método também seleciona a configuração padrão (primeiro) e a configuração alternativa 0 para cada interface nessa configuração.

O código de modelo armazena o endereço do objeto de dispositivo de destino USB (recebido por meio da chamada IWDFDriver::CreateDevice ) em um membro de dados privados da classe de retorno de chamada do dispositivo e libera essa referência chamando DriverSafeRelease. A contagem de referência do objeto de dispositivo de destino USB é mantida pela estrutura. O objeto está ativo enquanto o objeto do dispositivo estiver ativo. O driver cliente deve liberar a referência em IPnpCallbackHardware::OnReleaseHardware.

Depois que o driver cliente cria o objeto de dispositivo de destino USB, o driver chama métodos IWDFUsbTargetDevice para executar estas tarefas:

  • Recupere o dispositivo, a configuração, os descritores de interface e outras informações, como a velocidade do dispositivo.
  • Formate e envie solicitações de controle de E/S para o ponto de extremidade padrão.
  • Defina a política de energia para todo o dispositivo USB.

Para obter mais informações, consulte Trabalhando com dispositivos USB no UMDF. O exemplo de código a seguir mostra a implementação 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-fonte da fila

O objeto de fila da estrutura representa a fila de E/S de um objeto de dispositivo de estrutura específico. O código-fonte completo do objeto queue está em IoQueue.h e IoQueue.c.

IoQueue.h

O arquivo de cabeçalho IoQueue.h declara a classe de retorno de chamada de fila.

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;

};

No exemplo de código anterior, o driver do cliente declara a classe de retorno de chamada de fila. Quando instanciado, o objeto é associado ao objeto de fila da estrutura que manipula a maneira como as solicitações são enviadas para o driver cliente. A classe define dois métodos que criam e inicializam o objeto de fila da estrutura. O método estático CreateInstanceAndInitialize cria uma instância da classe de retorno de chamada de fila e chama o método Initialize que cria e inicializa o objeto de fila da estrutura. Ele também especifica as opções de expedição para o objeto de fila.

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

O exemplo de código a seguir mostra a implementação do 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;
}

No exemplo de código anterior, o driver do cliente cria o objeto de fila da estrutura. A estrutura fornece o objeto de fila para lidar com o fluxo de solicitação para o driver cliente.

Para criar o objeto, o driver cliente chama IWDFDevice::CreateIoQueue na referência IWDFDevice obtida em uma chamada anterior para IWDFDriver::CreateDevice.

Na chamada IWDFDevice::CreateIoQueue , o driver do cliente especifica determinadas opções de configuração antes que a estrutura crie filas. Essas opções determinam se a fila é gerenciada por energia, permite solicitações de comprimento zero e atua como a fila padrão para o driver. O driver do cliente fornece este conjunto de informações:

  • Referência à classe de retorno de chamada de fila

    Especifica um ponteiro IUnknown para sua classe de retorno de chamada de fila. Isso cria uma parceria entre o objeto de fila da estrutura e o objeto de retorno de chamada da fila do driver cliente. Quando o Gerenciador de E/S recebe uma nova solicitação de um aplicativo, ele notifica a estrutura. Em seguida, a estrutura usa o ponteiro IUnknown para invocar os métodos públicos expostos pelo objeto de retorno de chamada de fila.

  • Fila padrão ou secundária

    A fila deve ser a fila padrão ou uma fila secundária. Se o objeto de fila da estrutura agir como a fila padrão, todas as solicitações serão adicionadas à fila. Uma fila secundária é dedicada a um tipo específico de solicitação. Se o driver do cliente solicitar uma fila secundária, o driver também deverá chamar o método IWDFDevice::ConfigureRequestDispatching para indicar o tipo de solicitação que a estrutura deve colocar na fila especificada. No código do modelo, o driver do cliente passa FALSE no parâmetro bDefaultQueue . Isso instrui o método a criar uma fila secundária e não a fila padrão. Posteriormente, ele chama IWDFDevice::ConfigureRequestDispatching para indicar que a fila deve ter apenas solicitações de controle de E/S do dispositivo (consulte o código de exemplo nesta seção).

  • Tipo de expedição

    O tipo de expedição de um objeto de fila determina como a estrutura fornece solicitações ao driver cliente. O mecanismo de entrega pode ser sequencial, em paralelo ou por um mecanismo personalizado definido pelo driver do cliente. Para uma fila sequencial, uma solicitação não é entregue até que o driver cliente conclua a solicitação anterior. No modo de expedição paralelo, a estrutura encaminha as solicitações assim que elas chegam do Gerenciador de E/S. Isso significa que o driver cliente pode receber uma solicitação durante o processamento de outra. No mecanismo personalizado, o cliente extrai manualmente a próxima solicitação do objeto de fila da estrutura quando o driver está pronto para processá-lo. No código do modelo, o driver do cliente solicita um modo de expedição paralelo.

  • Fila gerenciada por energia

    O objeto de fila da estrutura deve ser sincronizado com o PnP e o estado de energia do dispositivo. Se o dispositivo não estiver em Estado de trabalho, o objeto de fila da estrutura deixará de expedir todas as solicitações. Quando o dispositivo está em Estado de trabalho, o objeto de fila retoma a expedição. Em uma fila gerenciada por energia, a sincronização é executada pela estrutura; caso contrário, a unidade do cliente deve lidar com essa tarefa. No código do modelo, o cliente solicita uma fila gerenciada por energia.

  • Solicitações de comprimento zero permitidas

    Um driver de cliente pode instruir a estrutura a concluir solicitações de E/S com buffers de comprimento zero em vez de colocá-las na fila. No código do modelo, o cliente solicita a estrutura para concluir essas solicitações.

Um único objeto de fila de estrutura pode lidar com vários tipos de solicitações, como o controle de E/S de leitura, gravação e dispositivo e assim por diante. Um driver de cliente com base no código do modelo pode processar somente solicitações de controle de E/S do dispositivo. Para isso, a classe de retorno de chamada de fila do driver cliente implementa a interface IQueueCallbackDeviceIoControl e o método IQueueCallbackDeviceIoControl::OnDeviceIoControl . Isso permite que a estrutura invoque a implementação do driver cliente de IQueueCallbackDeviceIoControl::OnDeviceIoControl quando a estrutura processa uma solicitação de controle de E/S do dispositivo.

Para outros tipos de solicitações, o driver cliente deve implementar a interface IQueueCallbackXxxx correspondente. Por exemplo, se o driver cliente quiser lidar com solicitações de leitura, a classe de retorno de chamada de fila deverá implementar a interface IQueueCallbackRead e seu método IQueueCallbackRead::OnRead . Para obter informações sobre os tipos de solicitações e interfaces de retorno de chamada, consulte Funções de retorno de chamada de evento de fila de E/S.

O exemplo de código a seguir mostra a implementação 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;

}

Vamos ver como o mecanismo de fila funciona. Para se comunicar com o dispositivo USB, um aplicativo primeiro abre um identificador para o dispositivo e envia uma solicitação de controle de E/S do dispositivo chamando a função DeviceIoControl com um código de controle específico. Dependendo do tipo de código de controle, o aplicativo pode especificar buffers de entrada e saída nessa chamada. A chamada é eventualmente recebida pelo Gerenciador de E/S, que notifica a estrutura. A estrutura cria um objeto de solicitação de estrutura e o adiciona ao objeto de fila da estrutura. No código de modelo, como o objeto queue foi criado com o sinalizador WdfIoQueueDispatchParallel, o retorno de chamada é invocado assim que a solicitação é adicionada à fila.

Quando a estrutura invoca o retorno de chamada de evento do driver cliente, ela passa um identificador para o objeto de solicitação de estrutura que contém a solicitação (e seus buffers de entrada e saída) enviados pelo aplicativo. Além disso, ele envia um identificador para o objeto de fila de estrutura que contém essa solicitação. No retorno de chamada de evento, o driver do cliente processa a solicitação conforme necessário. O código de modelo simplesmente conclui a solicitação. O driver do cliente pode executar tarefas mais envolvidas. Por exemplo, se um aplicativo solicitar determinadas informações de dispositivo, no retorno de chamada de evento, o driver cliente poderá criar uma solicitação de controle USB e enviá-la para a pilha de driver USB para recuperar as informações do dispositivo solicitadas. As solicitações de controle USB são discutidas na Transferência de Controle USB.

Código-fonte de entrada do driver

No código do modelo, a entrada do driver é implementada no Dllsup.cpp.

Dllsup.cpp

Após a seção incluir, uma constante GUID para o driver cliente é declarada. Esse GUID deve corresponder ao GUID no arquivo de instalação do driver (INF).

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

O próximo bloco de código declara a fábrica de classes para o driver cliente.

class CMyDriverModule :
    public CAtlDllModuleT< CMyDriverModule >
{
};

CMyDriverModule _AtlModule;

O código de modelo usa suporte à ATL para encapsular código COM complexo. A fábrica de classes herda a classe de modelo CAtlDllModuleT que contém todo o código necessário para criar o driver cliente.

O snippet de código a seguir mostra a implementação 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);
}

Se o driver cliente implementar a função DllMain , o Windows considerará dllMain como o ponto de entrada para o módulo de driver do cliente. O Windows chama DllMain depois de carregar o módulo do driver cliente no WUDFHost.exe. O Windows chama dllMain novamente pouco antes do Windows descarregar o driver cliente na memória. DllMain pode alocar e liberar variáveis globais no nível do driver. No código do modelo, o driver do cliente inicializa e libera os recursos necessários para o rastreamento do WPP e invoca a implementação de DllMain da classe ATL.

O snippet de código a seguir mostra a implementação de DllGetClassObject.

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

No código de modelo, a fábrica de classes e DllGetClassObject são implementadas na ATL. O snippet de código anterior simplesmente invoca a implementação DllGetClassObject da ATL. Em geral, DllGetClassObject deve executar as seguintes tarefas:

  1. Verifique se o CLSID passado pela estrutura é o GUID para o driver cliente. A estrutura recupera o CLSID para o driver cliente do arquivo INF do driver. Ao validar, verifique se o GUID especificado corresponde ao fornecido no INF.
  2. Instancie a fábrica de classes implementada pelo driver cliente. No código do modelo, isso é encapsulado pela classe ATL.
  3. Obtenha um ponteiro para a interface IClassFactory da fábrica de classes e retorne o ponteiro recuperado para a estrutura.

Depois que o módulo do driver do cliente é carregado na memória, a estrutura chama a função DllGetClassObject fornecida pelo driver. Na chamada da estrutura para DllGetClassObject, a estrutura passa o CLSID que identifica o driver cliente e solicita um ponteiro para a interface IClassFactory de uma fábrica de classes. O driver cliente implementa a fábrica de classes que facilita a criação do retorno de chamada do driver. Portanto, o driver do cliente deve conter pelo menos uma fábrica de classes. Em seguida, a estrutura chama IClassFactory::CreateInstance e solicita um ponteiro IDriverEntry para a classe de retorno de chamada do driver.

Exports.def

Para que a estrutura chame DllGetClassObject, o driver cliente deve exportar a função de um arquivo .def. O arquivo já está incluído no projeto do Visual Studio.

; Exports.def : Declares the module parameters.

LIBRARY     "MyUSBDriver_UMDF_.DLL"

EXPORTS
        DllGetClassObject   PRIVATE

No snippet de código anterior de Export.def incluído no projeto do driver, o cliente fornece o nome do módulo do driver como LIBRARY e DllGetClassObject em EXPORTS. Para obter mais informações, consulte Exportando de uma DLL usando arquivos DEF.