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:
- Código-fonte de retorno de chamada do driver
- Código-fonte de retorno de chamada do dispositivo
- Código-fonte da fila
- Código-fonte da Entrada do Driver
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:
- Cria um objeto de driver na estrutura , que representa o driver do cliente.
- Solicita um ponteiro de interface IDriverEntry da fábrica de classes.
- Cria um objeto de dispositivo na estrutura.
- 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:
- 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.
- Fornece uma classe de retorno de chamada que implementa a interface IDriverEntry .
- Fornece uma classe de retorno de chamada que implementa interfaces IPnpCallbackXxx .
- 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):
- 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.
- Recupera informações sobre interfaces, como o número de pontos de extremidade.
- 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.
- 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:
- 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.
- Instancie a fábrica de classes implementada pelo driver cliente. No código do modelo, isso é encapsulado pela classe ATL.
- 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.