Escritura de una aplicación de escritorio de Windows basada en la plantilla de WinUSB
La manera más fácil de escribir una aplicación de escritorio de Windows que se comunica con un dispositivo USB es mediante la plantilla WinUSB de C/C++. Para esta plantilla, necesita un entorno integrado con el Kit de controladores de Windows (WDK) (con herramientas de depuración para Windows) y Microsoft Visual Studio (Professional o Ultimate). Puede usar la plantilla como punto de partida.
- Para configurar el entorno de desarrollo integrado, instale primero Microsoft Visual Studio Ultimate 2019 o Microsoft Visual Studio Professional 2019 e instale el WDK. Puede encontrar información sobre cómo configurar Visual Studio y WDK en la página de descarga de WDK.
- Las herramientas de depuración para Windows se incluyen al instalar el WDK. Para obtener más información, vea Descargar e instalar herramientas de depuración para Windows.
Para crear una aplicación a partir de la plantilla:
En el cuadro de diálogo Nuevo proyecto , en el cuadro de búsqueda de la parte superior, escriba USB.
En el panel central, seleccione WinUSB Application (Universal).
Seleccione Next (Siguiente).
Escriba un nombre de proyecto, elija una ubicación de guardado y seleccione Crear.
En las capturas de pantalla siguientes se muestra el cuadro de diálogo Nuevo proyecto para la plantilla Aplicación WinUSB (Universal).
En este tema se supone que el nombre del proyecto de Visual Studio es USB Application1.
Visual Studio crea un proyecto y una solución. Puede ver la solución, el proyecto y los archivos que pertenecen al proyecto en la ventana Explorador de soluciones, como se muestra en la captura de pantalla siguiente. (Si la ventana Explorador de soluciones no está visible, elija Explorador de soluciones en el menú Ver). La solución contiene un proyecto de aplicación de C++ denominado USB Application1.
El proyecto USB Application1 tiene archivos de origen para la aplicación. Si desea ver el código fuente de la aplicación, puede abrir cualquiera de los archivos que aparecen en Archivos de código fuente.
Agregue un proyecto de paquete de controladores a la solución. Seleccione y mantenga presionada (o haga clic con el botón derecho) en la solución (solución "Aplicación USB1") y, a continuación, seleccione Agregar>nuevo proyecto como se muestra en la captura de pantalla siguiente.
En el cuadro de diálogo Nuevo proyecto , en el cuadro de búsqueda de la parte superior, vuelva a escribir USB.
En el panel central, seleccione Paquete de controladores INF de WinUSB.
Seleccione Next (Siguiente).
Escriba un nombre de proyecto y seleccione Crear.
En las capturas de pantalla siguientes se muestra el cuadro de diálogo Nuevo proyecto para la plantilla Paquete de controladores INF de WinUSB .
En este tema se supone que el nombre del proyecto de Visual Studio es el paquete USB Application1.
El proyecto paquete de aplicación USB1 contiene un archivo INF que se usa para instalar el controlador de Winusb.sys proporcionado por Microsoft como controlador del dispositivo.
La Explorador de soluciones ahora debe contener ambos proyectos, como se muestra en la captura de pantalla siguiente.
En el archivo INF, USBApplication1.inf, busque este código:
%DeviceName% =USB_Install, USB\VID_vvvv&PID_pppp
Reemplace VID_vvvv&PID_pppp por el identificador de hardware del dispositivo. Obtenga el identificador de hardware de Administrador de dispositivos. En Administrador de dispositivos, vea las propiedades del dispositivo. En la pestaña Detalles , vea el valor de la propiedad Ids de hardware .
En la ventana Explorador de soluciones, seleccione y mantenga presionado (o haga clic con el botón derecho) solución "Aplicación USB1" (2 de 2 proyectos) y elija Configuration Manager. Elija una configuración y una plataforma para el proyecto de aplicación y el proyecto de paquete. En este ejercicio, elegimos Depurar y x64, como se muestra en la captura de pantalla siguiente.
Hasta ahora en este ejercicio, ha usado Visual Studio para crear los proyectos. A continuación, debe configurar el dispositivo al que está conectado el dispositivo. La plantilla requiere que el controlador Winusb esté instalado como controlador para el dispositivo.
El entorno de prueba y depuración puede tener:
Dos configuraciones de equipo: el equipo host y el equipo de destino. Desarrolla y compila el proyecto en Visual Studio en el equipo host. El depurador se ejecuta en el equipo host y está disponible en la interfaz de usuario de Visual Studio. Al probar y depurar la aplicación, el controlador se ejecuta en el equipo de destino.
Configuración de un solo equipo: el destino y el host se ejecutan en un equipo. Desarrolle y compile el proyecto en Visual Studio y ejecute el depurador y la aplicación.
Puede implementar, instalar, cargar y depurar la aplicación y el controlador siguiendo estos pasos:
Configuración de dos equipos
- Aprovisione el equipo de destino siguiendo las instrucciones de Aprovisionamiento de un equipo para la implementación y las pruebas de controladores. Nota: El aprovisionamiento crea un usuario en la máquina de destino denominada WDKRemoteUser. Una vez completado el aprovisionamiento, verá el modificador de usuario a WDKRemoteUser.
- En el equipo host, abra la solución en Visual Studio.
- En main.cpp, agregue esta línea antes de la llamada a OpenDevice.
system ("pause")
La línea hace que la aplicación se detenga cuando se inicie. Esto es útil en la depuración remota.
- En pch.h, incluya esta línea:
#include <cstdlib>
Esta instrucción include es necesaria para la
system()
llamada en el paso anterior.En la ventana Explorador de soluciones, seleccione y mantenga presionado (o haga clic con el botón derecho) paquete USB Application1 y elija Propiedades.
En la ventana Páginas de propiedades del paquete de aplicación USB1, en el panel izquierdo, vaya a Propiedades de configuración > Instalación de la implementación del controlador>, como se muestra en la captura de pantalla siguiente.
Active Quitar versiones anteriores del controlador antes de la implementación.
En Nombre de equipo remoto, seleccione el nombre del equipo que configuró para probar y depurar. En este ejercicio, se usa un equipo denominado dbg-target.
Seleccione Instalar o reinstalar y Comprobar. Seleccione Aplicar.
En la página de propiedades, vaya a Depuración de propiedades > de configuración y seleccione Herramientas de depuración para Windows – Depurador remoto, como se muestra en la captura de pantalla siguiente.
En el menú Compilar, seleccione Compilar solución. Visual Studio muestra el progreso de la compilación en la ventana Salida . (Si la ventana Salida no está visible, elija Salida en el menú Ver). En este ejercicio, hemos creado el proyecto para un sistema x64 que ejecuta Windows 10.
Seleccione Implementar solución en el menú Compilar .
En el equipo de destino, verá que se ejecutan scripts de instalación de controladores. Los archivos de controlador se copian en la carpeta %Systemdrive%\drivertest\drivers del equipo de destino. Compruebe que los archivos .inf, .cat, cert de prueba y .sys, y cualquier otro archivo necesario, estén presentes en la carpeta %systemdrive%\drivertest\drivers. El dispositivo debe aparecer en Administrador de dispositivos sin errores.
En el equipo host, verá este mensaje en la ventana Salida .
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 ==========
En el equipo host, vaya a x64 > Win8.1Debug en la carpeta de la solución.
Copie el archivo ejecutable de la aplicación UsbApplication1.exe en el equipo de destino.
En el equipo de destino, inicie la aplicación.
En el equipo host, en el menú Depurar , seleccione Asociar al proceso.
En la ventana, seleccione Depurador de modo de usuario de Windows (Herramientas de depuración para Windows) como transporte y el nombre del equipo de destino, en este caso dbg-target, como calificador como se muestra en esta imagen.
Seleccione la aplicación en la lista de Procesos disponibles y seleccione Adjuntar. Ahora puede depurar con la ventana Inmediato o mediante las opciones del menú Depurar .
Las instrucciones anteriores depuran la aplicación mediante herramientas de depuración para Windows: depurador remoto. Si desea usar el depurador remoto de Windows (el depurador que se incluye con Visual Studio), siga estas instrucciones:
- En el equipo de destino, agregue msvsmon.exe a la lista de aplicaciones permitidas a través del firewall.
- Inicie el Monitor de depuración remota de Visual Studio ubicado en C:\DriverTest\msvsmon\msvsmon.exe.
- Cree una carpeta de trabajo, como C:\remotetemp.
- Copie el archivo ejecutable de la aplicación UsbApplication1.exe en la carpeta de trabajo del equipo de destino.
- En el equipo host, en Visual Studio, haga clic con el botón derecho en el proyecto Paquete de aplicación USB1 y seleccione Descargar proyecto.
- Seleccione y mantenga presionado (o haga clic con el botón derecho) en el proyecto USB Application1 , en las propiedades del proyecto, expanda el nodo Propiedades de configuración y seleccione Depuración.
- Cambie Depurador para iniciar a Depurador remoto de Windows.
- Cambie la configuración del proyecto para ejecutar el ejecutable en un equipo remoto siguiendo las instrucciones indicadas en Depuración remota de un proyecto compilado localmente. Asegúrese de que las propiedades Directorio de trabajo y Comando remoto reflejan la carpeta en el equipo de destino.
- Para depurar la aplicación, en el menú Compilar , seleccione Iniciar depuración o presione F5.
Configuración de un solo equipo:
Para compilar la aplicación y el paquete de instalación de controladores, elija Compilar solución en el menú Compilar . Visual Studio muestra el progreso de la compilación en la ventana Salida . (Si la ventana Salida no está visible, elija Salida en el menú Ver). En este ejercicio, hemos creado el proyecto para un sistema x64 que ejecuta Windows 10.
Para ver el paquete de controladores compilado, vaya en el Explorador de Windows a la carpeta USB Application1 y, a continuación, vaya a x64 > Depurar > paquete de aplicación USB1. El paquete de controladores contiene varios archivos: MyDriver.inf es un archivo de información que Windows usa al instalar el controlador, mydriver.cat es un archivo de catálogo que el instalador usa para comprobar la firma de prueba del paquete de controladores. Estos archivos se muestran en la siguiente captura de pantalla.
No hay ningún archivo de controlador incluido en el paquete. Esto se debe a que el archivo INF hace referencia al controlador integrada, Winusb.sys, que se encuentra en la carpeta Windows\System32.
Instale manualmente el controlador. En Administrador de dispositivos, actualice el controlador especificando inf en el paquete. Apunte al paquete de controladores ubicado en la carpeta de la solución, que se muestra en la sección anterior. Si ve el error
DriverVer set to a date in the future
, establezca la configuración > del proyecto inf2Cat > General > Use Local Time > Sí.Seleccione y mantenga presionado (o haga clic con el botón derecho) en el proyecto USB Application1 , en las propiedades del proyecto, expanda el nodo Propiedades de configuración y seleccione Depuración.
Cambie Depurador para iniciar a Depurador de Windows local.
Seleccione y mantenga presionado (o haga clic con el botón derecho) en el proyecto Paquete de aplicación USB1 y seleccione Descargar proyecto.
Para depurar la aplicación, en el menú Compilar , seleccione Iniciar depuración o presione F5.
La plantilla es un punto de partida para la aplicación de escritorio. El proyecto USB Application1 tiene archivos de origen device.cpp y main.cpp.
El archivo main.cpp contiene el punto de entrada de la aplicación, _tmain. Device.cpp contiene todas las funciones auxiliares que abren y cierran el identificador del dispositivo.
La plantilla también tiene un archivo de encabezado denominado device.h. Este archivo contiene definiciones para el GUID de la interfaz de dispositivo (descrito más adelante) y una estructura de DEVICE_DATA que almacena la información obtenida por la aplicación. Por ejemplo, almacena el identificador de interfaz winUSB obtenido por OpenDevice y se usa en las operaciones posteriores.
typedef struct _DEVICE_DATA {
BOOL HandlesOpen;
WINUSB_INTERFACE_HANDLE WinusbHandle;
HANDLE DeviceHandle;
TCHAR DevicePath[MAX_PATH];
} DEVICE_DATA, *PDEVICE_DATA;
Obtención de la ruta de acceso de la instancia del dispositivo: consulte RetrieveDevicePath en device.cpp.
Para acceder a un dispositivo USB, la aplicación crea un identificador de archivo válido para el dispositivo mediante una llamada a CreateFile. Para esa llamada, la aplicación debe obtener la instancia de ruta de acceso del dispositivo. Para obtener la ruta de acceso del dispositivo, la aplicación usa rutinas SetupAPI y especifica el GUID de la interfaz de dispositivo en el archivo INF que se usó para instalar Winusb.sys. Device.h declara una constante GUID denominada GUID_DEVINTERFACE_USBApplication1. Con esas rutinas, la aplicación enumera todos los dispositivos de la clase de interfaz de dispositivo especificada y recupera la ruta de acceso del dispositivo del 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;
}
En la función anterior, la aplicación obtiene la ruta de acceso del dispositivo mediante una llamada a estas rutinas:
SetupDiGetClassDevs para obtener un identificador para el conjunto de información del dispositivo, una matriz que contiene información sobre todos los dispositivos instalados que coinciden con la clase de interfaz de dispositivo especificada, GUID_DEVINTERFACE_USBApplication1. Cada elemento de la matriz denominada interfaz de dispositivo corresponde a un dispositivo instalado y registrado con el sistema. La clase de interfaz de dispositivo se identifica pasando el GUID de la interfaz de dispositivo que definió en el archivo INF. La función devuelve un identificador HDEVINFO al conjunto de información del dispositivo.
SetupDiEnumDeviceInterfaces para enumerar las interfaces de dispositivo en el conjunto de información del dispositivo y obtener información sobre la interfaz del dispositivo.
Esta llamada requiere los siguientes elementos:
Estructura de SP_DEVICE_INTERFACE_DATA asignada por el autor de la llamada inicial que tiene su miembro cbSize establecido en el tamaño de la estructura.
Identificador HDEVINFO del paso 1.
GUID de la interfaz de dispositivo que definió en el archivo INF.
SetupDiEnumDeviceInterfaces busca la matriz de conjuntos de información del dispositivo para el índice especificado de la interfaz del dispositivo y rellena la estructura SP_DEVICE_INTERFACE_DATA inicializada con datos básicos sobre la interfaz.
Para enumerar todas las interfaces de dispositivo del conjunto de información del dispositivo, llame a SetupDiEnumDeviceInterfaces en un bucle hasta que la función devuelva FALSE y se ERROR_NO_MORE_ITEMS el código de error. El código de error ERROR_NO_MORE_ITEMS se puede recuperar llamando a GetLastError. Con cada iteración, incremente el índice de miembro.
Como alternativa, puedes llamar a SetupDiEnumDeviceInfo que enumera el conjunto de información del dispositivo y devuelve información sobre los elementos de la interfaz del dispositivo, especificados por el índice, en una estructura de SP_DEVINFO_DATA asignada por el autor de la llamada. A continuación, puede pasar una referencia a esta estructura en el parámetro DeviceInfoData de la función SetupDiEnumDeviceInterfaces .
SetupDiGetDeviceInterfaceDetail para obtener datos detallados de la interfaz del dispositivo. La información se devuelve en una estructura SP_DEVICE_INTERFACE_DETAIL_DATA . Dado que el tamaño de la estructura de SP_DEVICE_INTERFACE_DETAIL_DATA varía, se llama a SetupDiGetDeviceInterfaceDetail dos veces. La primera llamada obtiene el tamaño del búfer que se va a asignar a la estructura de SP_DEVICE_INTERFACE_DETAIL_DATA . La segunda llamada rellena el búfer asignado con información detallada sobre la interfaz.
- Llama al parámetro SetupDiGetDeviceInterfaceDetail con el parámetro DeviceInterfaceDetailData establecido en NULL. La función devuelve el tamaño de búfer correcto en el parámetro requiredlength . Se produce un error en esta llamada con el código de error ERROR_INSUFFICIENT_BUFFER. Se espera este código de error.
- Asigna memoria para una estructura de SP_DEVICE_INTERFACE_DETAIL_DATA en función del tamaño de búfer correcto que se recupera en el parámetro requiredlength .
- Llama de nuevo a SetupDiGetDeviceInterfaceDetail y le pasa una referencia a la estructura inicializada en el parámetro DeviceInterfaceDetailData . Cuando se devuelve la función, la estructura se rellena con información detallada sobre la interfaz. La ruta de acceso del dispositivo está en el miembro DevicePath de la estructura SP_DEVICE_INTERFACE_DETAIL_DATA.
Consulte OpenDevice en device.cpp.
Para interactuar con el dispositivo, necesita un identificador de interfaz winUSB para la primera interfaz (predeterminada) del dispositivo. El código de plantilla obtiene el identificador de archivo y el identificador de interfaz winUSB y los almacena en la estructura 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;
}
- La aplicación llama a CreateFile para crear un identificador de archivo para el dispositivo especificando la ruta de acceso del dispositivo recuperada anteriormente. Usa la marca FILE_FLAG_OVERLAPPED porque WinUSB depende de esta configuración.
- Mediante el uso del identificador de archivo para el dispositivo, la aplicación crea un identificador de interfaz winUSB. Las funciones winUSB usan este identificador para identificar el dispositivo de destino en lugar del identificador de archivo. Para obtener un identificador de interfaz winUSB, la aplicación llama a WinUsb_Initialize pasando el identificador de archivo. Use el identificador recibido en las llamadas posteriores para obtener información del dispositivo y para enviar solicitudes de E/S al dispositivo.
El código de plantilla implementa código para liberar el identificador de archivo y el identificador de interfaz winUSB para el dispositivo.
- CloseHandle para liberar el identificador creado por CreateFile, como se describe en la sección Crear un identificador de archivo para el dispositivo de este tutorial.
- WinUsb_Free liberar el identificador de interfaz winUSB para el dispositivo, que devuelve 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;
}
A continuación, lea estos temas para enviar información del dispositivo y enviar transferencias de datos al dispositivo:
Acceso a un dispositivo USB mediante funciones winUSB
Obtenga información sobre cómo consultar el dispositivo para obtener información específica de USB, como la velocidad del dispositivo, los descriptores de interfaz, los puntos de conexión relacionados y sus canalizaciones.
Envío de transferencias isócronos USB desde una aplicación de escritorio winUSB
Transferir datos hacia y desde puntos de conexión isócronos de un dispositivo USB.