Escrever um aplicativo da área de trabalho do Windows com base no modelo do WinUSB
A maneira mais fácil de escrever um aplicativo da área de trabalho do Windows que se comunica com um dispositivo USB é usando o modelo C/C++ WinUSB. Para este modelo, você precisa de um ambiente integrado com o WDK (Kit de Driver do Windows) (com Ferramentas de Depuração para Windows) e o Microsoft Visual Studio (Professional ou Ultimate). Você pode usar o modelo como ponto de partida.
Antes de começar
- Para configurar o ambiente de desenvolvimento integrado, primeiro instale Microsoft Visual Studio Ultimate 2019 ou Microsoft Visual Studio Professional 2019 e, em seguida, instale o WDK. Você pode encontrar informações sobre como configurar o Visual Studio e o WDK na página de download do WDK.
- As Ferramentas de Depuração para Windows são incluídas quando você instala o WDK. Para obter mais informações, consulte Baixar e instalar ferramentas de depuração para Windows.
Criando um aplicativo WinUSB
Para criar um aplicativo com base no modelo:
Na caixa de diálogo Novo Projeto , na caixa de pesquisa na parte superior, digite USB.
No painel central, selecione Aplicativo WinUSB (Universal).
Selecione Avançar.
Insira um nome de projeto, escolha um local de salvamento e selecione Criar.
As capturas de tela a seguir mostram a caixa de diálogo Novo Projeto para o modelo aplicativo WinUSB (Universal).
Este tópico pressupõe que o nome do projeto do Visual Studio seja Application1 USB.
O Visual Studio cria um projeto e uma solução. Você pode ver a solução, o projeto e os arquivos que pertencem ao projeto na janela Gerenciador de Soluções, conforme mostrado na captura de tela a seguir. (Se a janela Gerenciador de Soluções não estiver visível, escolha Gerenciador de Soluções no menu Exibir.) A solução contém um projeto de aplicativo C++ chamado Aplicativo USB1.
O projeto Application1 USB tem arquivos de origem para o aplicativo. Se você quiser examinar o código-fonte do aplicativo, poderá abrir qualquer um dos arquivos que aparecem em Arquivos de Origem.
Adicione um projeto de pacote de driver à solução. Selecione e segure (ou clique com o botão direito do mouse) a solução (Solução 'Aplicativo USB1') e selecione Adicionar>Novo Projeto , conforme mostrado na captura de tela a seguir.
Na caixa de diálogo Novo Projeto , na caixa de pesquisa na parte superior, digite novamente USB.
No painel central, selecione Pacote de Driver INF do WinUSB.
Selecione Avançar.
Insira um nome de projeto e selecione Criar.
As capturas de tela a seguir mostram a caixa de diálogo Novo Projeto para o modelo pacote de driver INF do WinUSB .
Este tópico pressupõe que o nome do projeto do Visual Studio seja Usb Application1 Package.
O projeto pacote application1 USB contém um arquivo INF que é usado para instalar o driver de Winusb.sys fornecido pela Microsoft como o driver do dispositivo.
Sua Gerenciador de Soluções agora deve conter ambos os projetos, conforme mostrado na captura de tela a seguir.
No arquivo INF, USBApplication1.inf, localize este código:
%DeviceName% =USB_Install, USB\VID_vvvv&PID_pppp
Substitua VID_vvvv&PID_pppp pela ID de hardware do dispositivo. Obtenha a ID de hardware do Gerenciador de Dispositivos. Em Gerenciador de Dispositivos, exiba as propriedades do dispositivo. Na guia Detalhes , exiba o valor da propriedade IDs de hardware .
Na janela Gerenciador de Soluções, selecione e segure (ou clique com o botão direito do mouse) solução 'APLICATIVO USB1' (2 de 2 projetos)e escolha Configuration Manager. Escolha uma configuração e uma plataforma para o projeto de aplicativo e o projeto do pacote. Neste exercício, escolhemos Depurar e x64, conforme mostrado na captura de tela a seguir.
Compilar, implantar e depurar o projeto
Até agora neste exercício, você usou o Visual Studio para criar seus projetos. Em seguida, você precisa configurar o dispositivo ao qual o dispositivo está conectado. O modelo requer que o driver do Winusb seja instalado como o driver do seu dispositivo.
Seu ambiente de teste e depuração pode ter:
Configuração de dois computadores: o computador host e o computador de destino. Você desenvolve e cria seu projeto no Visual Studio no computador host. O depurador é executado no computador host e está disponível na interface do usuário do Visual Studio. Quando você testa e depura o aplicativo, o driver é executado no computador de destino.
Configuração de computador único: seu destino e host são executados em um computador. Você desenvolve e cria seu projeto no Visual Studio e executa o depurador e o aplicativo.
Você pode implantar, instalar, carregar e depurar seu aplicativo e o driver seguindo estas etapas:
Configuração de dois computadores
- Provisione seu computador de destino seguindo as instruções em Provisionar um computador para implantação e teste de driver. Nota: O provisionamento cria um usuário no computador de destino chamado WDKRemoteUser. Após a conclusão do provisionamento, você verá o usuário alternar para WDKRemoteUser.
- No computador host, abra sua solução no Visual Studio.
- Em main.cpp, adicione essa linha antes da chamada de OpenDevice.
system ("pause")
A linha faz com que o aplicativo pause quando iniciado. Isso é útil na depuração remota.
- Em pch.h, inclua esta linha:
#include <cstdlib>
Essa instrução include é necessária para a
system()
chamada na etapa anterior.Na janela Gerenciador de Soluções, selecione e segure (ou clique com o botão direito do mouse) pacote USB Application1 e escolha Propriedades.
Na janela Páginas de Propriedades do Pacote do Aplicativo USB1 , no painel esquerdo, navegue até Propriedades de Configuração > Implantação de Instalação > do Driver, conforme mostrado na captura de tela a seguir.
Marque Remover versões anteriores do driver antes da implantação.
Para Nome do Computador Remoto, selecione o nome do computador que você configurou para teste e depuração. Neste exercício, usamos um computador chamado dbg-target.
Selecione Instalar/Reinstalar e Verificar. Escolha Aplicar.
Na página de propriedades, navegue até Propriedades > de Configuração Depuração e selecione Ferramentas de Depuração para Windows – Depurador Remoto, conforme mostrado na captura de tela a seguir.
Selecione Solução de compilação no menu Compilação. O Visual Studio exibe o progresso do build na janela Saída . (Se a janela Saída não estiver visível, escolha Saída no menu Exibir.) Neste exercício, criamos o projeto para um sistema x64 que executa Windows 10.
Selecione Implantar Solução no menu Compilar .
No computador de destino, você verá scripts de instalação do driver em execução. Os arquivos de driver são copiados para a pasta %Systemdrive%\drivertest\drivers no computador de destino. Verifique se os arquivos .inf, .cat, test cert e .sys e outros arquivos necessários estão presentes na pasta %systemdrive%\drivertest\drivers. O dispositivo deve aparecer em Gerenciador de Dispositivos sem erros.
No computador host, você verá essa mensagem na janela Saída .
Deploying driver files for project
"<path>\visual studio 14\Projects\USB Application1\USB Application1 Package\USB Application1 Package.vcxproj".
Deployment may take a few minutes...
========== Build: 1 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
Para depurar o aplicativo
No computador host, navegue até x64 > Win8.1Debug na pasta da solução.
Copie o executável do aplicativo UsbApplication1.exe para o computador de destino.
No computador de destino, inicie o aplicativo.
No computador host, no menu Depurar , selecione Anexar ao processo.
Na janela, selecione Depurador do Modo de Usuário do Windows (Ferramentas de Depuração para Windows) como o transporte e o nome do computador de destino, nesse caso dbg-target, como o qualificador, conforme mostrado nesta imagem.
Selecione o aplicativo na lista de Processos Disponíveis e selecione Anexar. Agora você pode depurar usando a Janela Imediata ou usando as opções no menu Depurar .
As instruções anteriores depuram o aplicativo usando as Ferramentas de Depuração para Windows – Depurador Remoto. Se você quiser usar o Depurador Remoto do Windows (o depurador incluído no Visual Studio), siga estas instruções:
- No computador de destino, adicione msvsmon.exe à lista de aplicativos permitidos por meio do Firewall.
- Inicie o Monitor de Depuração Remota do Visual Studio localizado em C:\DriverTest\msvsmon\msvsmon.exe.
- Crie uma pasta de trabalho, como C:\remotetemp.
- Copie o executável do aplicativo UsbApplication1.exe para a pasta de trabalho no computador de destino.
- No computador host, no Visual Studio, clique com o botão direito do mouse no projeto pacote USB Application1 e selecione Descarregar Projeto.
- Selecione e segure (ou clique com o botão direito do mouse) o projeto APPLICATION1 USB , nas propriedades do projeto, expanda o nó Propriedades de Configuração e selecione Depuração.
- Altere Depurador para iniciar para Depurador Remoto do Windows.
- Altere as configurações do projeto para executar o executável em um computador remoto seguindo as instruções fornecidas em Depuração Remota de um Projeto Criado Localmente. Verifique se as propriedades Diretório de Trabalho e Comando Remoto refletem a pasta no computador de destino.
- Para depurar o aplicativo, no menu Compilar , selecione Iniciar Depuração ou pressione F5.
Configuração de computador único:
Para criar seu aplicativo e o pacote de instalação do driver, escolha Compilar Solução no menu Compilar . O Visual Studio exibe o progresso do build na janela Saída . (Se a janela Saída não estiver visível, escolha Saída no menu Exibir.) Neste exercício, criamos o projeto para um sistema x64 que executa Windows 10.
Para ver o pacote de driver criado, navegue no Windows Explorer até a pasta Application1 USB e navegue até o Pacote de Aplicativo USB1 de Depuração > x64>. O pacote de driver contém vários arquivos: MyDriver.inf é um arquivo de informações que o Windows usa quando você instala o driver, mydriver.cat é um arquivo de catálogo que o instalador usa para verificar a assinatura de teste para o pacote de driver. Esses arquivos são mostrados na captura de tela a seguir.
Não há nenhum arquivo de driver incluído no pacote. Isso ocorre porque o arquivo INF faz referência ao driver in-box, Winusb.sys, encontrado na pasta Windows\System32.
Instale manualmente o driver. Em Gerenciador de Dispositivos, atualize o driver especificando o INF no pacote. Aponte para o pacote de driver localizado na pasta da solução, mostrada na seção anterior. Se você vir o erro
DriverVer set to a date in the future
, defina configurações > de projeto do Pacote INF Inf2Cat > Uso Geral > Hora > Local Sim.Selecione e segure (ou clique com o botão direito do mouse) o projeto APPLICATION1 USB , nas propriedades do projeto, expanda o nó Propriedades de Configuração e selecione Depuração.
Altere Depurador para iniciar para Depurador Local do Windows.
Selecione e segure (ou clique com o botão direito do mouse) no projeto pacote USB Application1 e selecione Descarregar Projeto.
Para depurar o aplicativo, no menu Compilar , selecione Iniciar Depuração ou pressione F5.
Discussão de código de modelo
O modelo é um ponto de partida para seu aplicativo da área de trabalho. O projeto Application1 USB tem arquivos de origem device.cpp e main.cpp.
O arquivo main.cpp contém o ponto de entrada do aplicativo, _tmain. O device.cpp contém todas as funções auxiliares que abrem e fecham o identificador do dispositivo.
O modelo também tem um arquivo de cabeçalho chamado device.h. Esse arquivo contém definições para o GUID da interface do dispositivo (discutido posteriormente) e uma estrutura de DEVICE_DATA que armazena informações obtidas pelo aplicativo. Por exemplo, ele armazena o identificador de interface WinUSB obtido pelo OpenDevice e usado em operações subsequentes.
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
} DEVICE_DATA, *PDEVICE_DATA;
Obtendo o caminho da instância para o dispositivo – consulte RetrieveDevicePath em device.cpp
Para acessar um dispositivo USB, o aplicativo cria um identificador de arquivo válido para o dispositivo chamando CreateFile. Para essa chamada, o aplicativo deve obter a instância de caminho do dispositivo. Para obter o caminho do dispositivo, o aplicativo usa rotinas SetupAPI e especifica o GUID da interface do dispositivo no arquivo INF que foi usado para instalar Winusb.sys. Device.h declara uma constante GUID chamada GUID_DEVINTERFACE_USBApplication1. Usando essas rotinas, o aplicativo enumera todos os dispositivos na classe de interface do dispositivo especificada e recupera o caminho do dispositivo.
HRESULT
RetrieveDevicePath(
_Out_bytecap_(BufLen) LPTSTR DevicePath,
_In_ ULONG BufLen,
_Out_opt_ PBOOL FailureDeviceNotFound
)
/*++
Routine description:
Retrieve the device path that can be used to open the WinUSB-based device.
If multiple devices have the same device interface GUID, there is no
guarantee of which one will be returned.
Arguments:
DevicePath - On successful return, the path of the device (use with CreateFile).
BufLen - The size of DevicePath's buffer, in bytes
FailureDeviceNotFound - TRUE when failure is returned due to no devices
found with the correct device interface (device not connected, driver
not installed, or device is disabled in Device Manager); FALSE
otherwise.
Return value:
HRESULT
--*/
{
BOOL bResult = FALSE;
HDEVINFO deviceInfo;
SP_DEVICE_INTERFACE_DATA interfaceData;
PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
ULONG length;
ULONG requiredLength=0;
HRESULT hr;
if (NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = FALSE;
}
//
// Enumerate all devices exposing the interface
//
deviceInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USBApplication1,
NULL,
NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (deviceInfo == INVALID_HANDLE_VALUE) {
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
//
// Get the first interface (index 0) in the result set
//
bResult = SetupDiEnumDeviceInterfaces(deviceInfo,
NULL,
&GUID_DEVINTERFACE_USBApplication1,
0,
&interfaceData);
if (FALSE == bResult) {
//
// We would see this error if no devices were found
//
if (ERROR_NO_MORE_ITEMS == GetLastError() &&
NULL != FailureDeviceNotFound) {
*FailureDeviceNotFound = TRUE;
}
hr = HRESULT_FROM_WIN32(GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Get the size of the path string
// We expect to get a failure with insufficient buffer
//
bResult = SetupDiGetDeviceInterfaceDetail(deviceInfo,
&interfaceData,
NULL,
0,
&requiredLength,
NULL);
if (FALSE == bResult && ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
hr = HRESULT_FROM_WIN32(GetLastError());
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Allocate temporary space for SetupDi structure
//
detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)
LocalAlloc(LMEM_FIXED, requiredLength);
if (NULL == detailData)
{
hr = E_OUTOFMEMORY;
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
length = requiredLength;
//
// Get the interface's path string
//
bResult = SetupDiGetDeviceInterfaceDetail(deviceInfo,
&interfaceData,
detailData,
length,
&requiredLength,
NULL);
if(FALSE == bResult)
{
hr = HRESULT_FROM_WIN32(GetLastError());
LocalFree(detailData);
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
//
// Give path to the caller. SetupDiGetDeviceInterfaceDetail ensured
// DevicePath is NULL-terminated.
//
hr = StringCbCopy(DevicePath,
BufLen,
detailData->DevicePath);
LocalFree(detailData);
SetupDiDestroyDeviceInfoList(deviceInfo);
return hr;
}
Na função anterior, o aplicativo obtém o caminho do dispositivo chamando estas rotinas:
SetupDiGetClassDevs para obter um identificador para o conjunto de informações do dispositivo, uma matriz que contém informações sobre todos os dispositivos instalados que correspondem à classe de interface do dispositivo especificada, GUID_DEVINTERFACE_USBApplication1. Cada elemento na matriz chamado interface do dispositivo corresponde a um dispositivo instalado e registrado com o sistema. A classe de interface do dispositivo é identificada passando o GUID da interface do dispositivo que você definiu no arquivo INF. A função retorna um identificador HDEVINFO para o conjunto de informações do dispositivo.
SetupDiEnumDeviceInterfaces para enumerar as interfaces do dispositivo no conjunto de informações do dispositivo e obter informações sobre a interface do dispositivo.
Essa chamada requer os seguintes itens:
Uma estrutura de SP_DEVICE_INTERFACE_DATA alocada pelo chamador inicializada que tem seu membro cbSize definido como o tamanho da estrutura.
O identificador HDEVINFO da etapa 1.
O GUID da interface do dispositivo que você definiu no arquivo INF.
SetupDiEnumDeviceInterfaces pesquisa a matriz do conjunto de informações do dispositivo para o índice especificado da interface do dispositivo e preenche a estrutura de SP_DEVICE_INTERFACE_DATA inicializada com dados básicos sobre a interface.
Para enumerar todas as interfaces de dispositivo no conjunto de informações do dispositivo, chame SetupDiEnumDeviceInterfaces em um loop até que a função retorne FALSE e o código de erro para a falha seja ERROR_NO_MORE_ITEMS. O código de erro ERROR_NO_MORE_ITEMS pode ser recuperado chamando GetLastError. A cada iteração, incremente o índice de membro.
Como alternativa, você pode chamar SetupDiEnumDeviceInfo que enumera o conjunto de informações do dispositivo e retorna informações sobre elementos de interface do dispositivo, especificados pelo índice, em uma estrutura de SP_DEVINFO_DATA alocada pelo chamador. Em seguida, você pode passar uma referência a essa estrutura no parâmetro DeviceInfoData da função SetupDiEnumDeviceInterfaces .
SetupDiGetDeviceInterfaceDetail para obter dados detalhados para a interface do dispositivo. As informações são retornadas em uma estrutura SP_DEVICE_INTERFACE_DETAIL_DATA . Como o tamanho da estrutura SP_DEVICE_INTERFACE_DETAIL_DATA varia, SetupDiGetDeviceInterfaceDetail é chamado duas vezes. A primeira chamada obtém o tamanho do buffer a ser alocado para a estrutura SP_DEVICE_INTERFACE_DETAIL_DATA . A segunda chamada preenche o buffer alocado com informações detalhadas sobre a interface.
- Chama SetupDiGetDeviceInterfaceDetail com o parâmetro DeviceInterfaceDetailData definido como NULL. A função retorna o tamanho correto do buffer no parâmetro requiredlength . Essa chamada falha com o código de erro ERROR_INSUFFICIENT_BUFFER. Esse código de erro é esperado.
- Aloca memória para uma estrutura SP_DEVICE_INTERFACE_DETAIL_DATA com base no tamanho correto do buffer recuperado no parâmetro requiredlength .
- Chama SetupDiGetDeviceInterfaceDetail novamente e passa uma referência à estrutura inicializada no parâmetro DeviceInterfaceDetailData . Quando a função retorna, a estrutura é preenchida com informações detalhadas sobre a interface. O caminho do dispositivo está no membro DevicePath da estrutura SP_DEVICE_INTERFACE_DETAIL_DATA.
Criando um identificador de arquivo para o dispositivo
Consulte OpenDevice em device.cpp.
Para interagir com o dispositivo, o precisa de um identificador de interface WinUSB para a primeira interface (padrão) no dispositivo. O código de modelo obtém o identificador de arquivo e o identificador da interface WinUSB e os armazena na estrutura DEVICE_DATA.
HRESULT
OpenDevice(
_Out_ PDEVICE_DATA DeviceData,
_Out_opt_ PBOOL FailureDeviceNotFound
)
/*++
Routine description:
Open all needed handles to interact with the device.
If the device has multiple USB interfaces, this function grants access to
only the first interface.
If multiple devices have the same device interface GUID, there is no
guarantee of which one will be returned.
Arguments:
DeviceData - Struct filled in by this function. The caller should use the
WinusbHandle to interact with the device, and must pass the struct to
CloseDevice when finished.
FailureDeviceNotFound - TRUE when failure is returned due to no devices
found with the correct device interface (device not connected, driver
not installed, or device is disabled in Device Manager); FALSE
otherwise.
Return value:
HRESULT
--*/
{
HRESULT hr = S_OK;
BOOL bResult;
DeviceData->HandlesOpen = FALSE;
hr = RetrieveDevicePath(DeviceData->DevicePath,
sizeof(DeviceData->DevicePath),
FailureDeviceNotFound);
if (FAILED(hr)) {
return hr;
}
DeviceData->DeviceHandle = CreateFile(DeviceData->DevicePath,
GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_WRITE | FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE == DeviceData->DeviceHandle) {
hr = HRESULT_FROM_WIN32(GetLastError());
return hr;
}
bResult = WinUsb_Initialize(DeviceData->DeviceHandle,
&DeviceData->WinusbHandle);
if (FALSE == bResult) {
hr = HRESULT_FROM_WIN32(GetLastError());
CloseHandle(DeviceData->DeviceHandle);
return hr;
}
DeviceData->HandlesOpen = TRUE;
return hr;
}
- O aplicativo chama CreateFile para criar um identificador de arquivo para o dispositivo especificando o caminho do dispositivo recuperado anteriormente. Ele usa o sinalizador FILE_FLAG_OVERLAPPED porque o WinUSB depende dessa configuração.
- Usando o identificador de arquivo para o dispositivo, o aplicativo cria um identificador de interface WinUSB. As funções do WinUSB usam esse identificador para identificar o dispositivo de destino em vez do identificador de arquivo. Para obter um identificador de interface WinUSB, o aplicativo chama WinUsb_Initialize passando o identificador de arquivo. Use o identificador recebido nas chamadas subsequentes para obter informações do dispositivo e enviar solicitações de E/S para o dispositivo.
Liberar os identificadores do dispositivo – consulte CloseDevice em device.cpp
O código de modelo implementa o código para liberar o identificador de arquivo e o identificador da interface WinUSB para o dispositivo.
- CloseHandle para liberar o identificador que foi criado por CreateFile, conforme descrito na seção Criar um identificador de arquivo para o dispositivo deste passo a passo.
- WinUsb_Free liberar o identificador de interface do WinUSB para o dispositivo, que é retornado por WinUsb_Initialize .
VOID
CloseDevice(
_Inout_ PDEVICE_DATA DeviceData
)
/*++
Routine description:
Perform required cleanup when the device is no longer needed.
If OpenDevice failed, do nothing.
Arguments:
DeviceData - Struct filled in by OpenDevice
Return value:
None
--*/
{
if (FALSE == DeviceData->HandlesOpen) {
//
// Called on an uninitialized DeviceData
//
return;
}
WinUsb_Free(DeviceData->WinusbHandle);
CloseHandle(DeviceData->DeviceHandle);
DeviceData->HandlesOpen = FALSE;
return;
}
Próximas etapas
Em seguida, leia estes tópicos para enviar informações do dispositivo e enviar transferências de dados para o dispositivo:
Acessar um dispositivo USB usando funções WinUSB
Saiba mais sobre como consultar o dispositivo para obter informações específicas de USB, como velocidade do dispositivo, descritores de interface, pontos de extremidade relacionados e seus pipes.
Enviar transferências isócronas USB de um aplicativo da área de trabalho do WinUSB
Transferir dados de e para pontos de extremidade isócronos de um dispositivo USB.