Создание классического приложения для Windows на основе шаблона WinUSB

Самый простой способ написать классическое приложение Windows, которое взаимодействует с USB-устройством, — использовать шаблон WinUSB на C/C++. Для этого шаблона требуется интегрированная среда с пакетом драйверов Windows (WDK) (со средствами отладки для Windows) и Microsoft Visual Studio (Professional или Ultimate). Шаблон можно использовать в качестве отправной точки.

Перед началом работы

  • Чтобы настроить интегрированную среду разработки, сначала установите Microsoft Visual Studio Ultimate 2019 или Microsoft Visual Studio Professional 2019, а затем установите WDK. Сведения о настройке Visual Studio и WDK можно найти на странице скачивания WDK.
  • Средства отладки для Windows включаются при установке WDK. Дополнительные сведения см. в статье Скачивание и установка средств отладки для Windows.

Создание приложения WinUSB

Чтобы создать приложение на основе шаблона, выполните следующие действия.

  1. В диалоговом окне Новый проект в поле поиска вверху введите USB.

  2. В средней области выберите Приложение WinUSB (универсальное).

  3. Выберите Далее.

  4. Введите имя проекта, выберите расположение для сохранения и нажмите кнопку Создать.

    На снимках экрана ниже показано диалоговое окно "Создать проект" для шаблона "Приложение WinUSB (универсальное приложение).

    Первый экран создания нового проекта в шаблоне winusb.

    Шаблон winusb создание нового проекта второй экран.

    В этом разделе предполагается, что проект Visual Studio имеет имя USB Application1.

    Visual Studio создает один проект и решение. Решение, проект и файлы, принадлежащие проекту, отображаются в окне Обозреватель решений, как показано на следующем снимке экрана. (Если окно Обозреватель решений не отображается, выберите Обозреватель решений в меню Вид.) Решение содержит проект приложения C++ с именем USB Application1.

    Обозреватель решений шаблонов winusb 1.

    Проект USB Application1 содержит исходные файлы для приложения. Если вы хотите просмотреть исходный код приложения, можно открыть любой из файлов, которые отображаются в разделе Исходные файлы.

  5. Добавьте в решение проект пакета драйверов. Выберите и удерживайте (или щелкните правой кнопкой мыши) решение (решение USB Application1), а затем выберите Добавить>новый проект , как показано на следующем снимке экрана.

    Добавление второго проекта для создания шаблона winusb.

  6. В диалоговом окне Новый проект в поле поиска вверху еще раз введите USB.

  7. В средней области выберите Пакет драйвера WinUSB INF.

  8. Выберите Далее.

  9. Введите имя проекта, а затем нажмите кнопку Создать.

    На следующих снимках экрана показано диалоговое окно Новый проект для шаблона пакета драйвера WinUSB INF .

    Первый экран создания второго проекта winusb шаблона.

    Второй экран создания второго проекта шаблона winusb.

    В этом разделе предполагается, что проект Visual Studio называется USB Application1 Package.

    Проект пакета USB Application1 содержит INF-файл, который используется для установки предоставленного корпорацией Майкрософт Winusb.sys драйвера в качестве драйвера устройства.

    Теперь Обозреватель решений должны содержать оба проекта, как показано на следующем снимке экрана.

    Обозреватель решений шаблонов winusb 2.

  10. В INF-файле USBApplication1.inf найдите следующий код: %DeviceName% =USB_Install, USB\VID_vvvv&PID_pppp

  11. Замените VID_vvvv&PID_pppp идентификатором оборудования для устройства. Получите идентификатор оборудования из диспетчер устройств. В диспетчер устройств просмотрите свойства устройства. На вкладке Сведения просмотрите значение свойства Hardware Ids .

  12. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) Решение "USB Application1" (2 из 2 проектов) и выберите Configuration Manager. Выберите конфигурацию и платформу для проекта приложения и проекта пакета. В этом упражнении мы выбираем Отладка и x64, как показано на следующем снимке экрана.

Снимок экрана: окно

Сборка, развертывание и отладка проекта

До сих пор в этом упражнении вы использовали Visual Studio для создания проектов. Затем необходимо настроить устройство, к которому подключено устройство. Шаблон требует, чтобы драйвер Winusb был установлен в качестве драйвера для вашего устройства.

Среда тестирования и отладки может иметь:

  • Установка двух компьютеров: главный и целевой компьютер. Вы разрабатываете и создаете проект в Visual Studio на хост-компьютере. Отладчик работает на хост-компьютере и доступен в пользовательском интерфейсе Visual Studio. При тестировании и отладке приложения драйвер запускается на целевом компьютере.

  • Настройка на одном компьютере. Целевой объект и узел работают на одном компьютере. Вы разрабатываете и создаете проект в Visual Studio, а также запускаете отладчик и приложение.

Вы можете развернуть, установить, загрузить и отладить приложение и драйвер, выполнив следующие действия.

  • Установка двух компьютеров

    1. Подготовьте целевой компьютер, следуя инструкциям в статье Подготовка компьютера для развертывания и тестирования драйверов. Примечание: При подготовке на целевом компьютере создается пользователь с именем WDKRemoteUser. После завершения подготовки вы увидите, что пользователь переключится на WDKRemoteUser.
    2. На хост-компьютере откройте решение в Visual Studio.
    3. В main.cpp добавьте эту строку перед вызовом OpenDevice.
    system ("pause")
    

    Строка приводит к приостановке приложения при запуске. Это полезно при удаленной отладке.

    1. В файле pch.h добавьте следующую строку:
    #include <cstdlib>
    

    Этот оператор include является обязательным для system() вызова на предыдущем шаге.

    1. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) пакет USB Application1 и выберите Свойства.

    2. В окне USB Application1 Package Property Pages (Страницы свойств пакета USB Application1 ) в левой области перейдите к разделу Свойства > конфигурации Драйвер Установка > развертывания, как показано на следующем снимке экрана.

    3. Установите флажок Удалить предыдущие версии драйверов перед развертыванием.

    4. В поле Имя удаленного компьютера выберите имя компьютера, настроенного для тестирования и отладки. В этом упражнении используется компьютер с именем dbg-target.

    5. Выберите Установить или переустановить и проверить. Нажмите кнопку Применить.

      Развертывание шаблона winusb.

    6. На странице свойств перейдите к свойствам > конфигурации Отладка и выберите Средства отладки для Windows — Удаленный отладчик, как показано на следующем снимке экрана.

      Удаленный отладчик шаблона winusb.

    7. Выберите Сборка решения в меню Сборка . Visual Studio отображает ход выполнения сборки в окне Вывод . (Если окно Вывод не отображается, выберите Вывод в меню Вид.) В этом упражнении мы создали проект для системы x64 под управлением Windows 10.

    8. Выберите Развернуть решение в меню Сборка .

На целевом компьютере вы увидите выполняемые скрипты установки драйверов. Файлы драйверов копируются в папку %Systemdrive%\drivertest\drivers на целевом компьютере. Убедитесь, что файлы .inf, .cat, test cert и .sys, а также все другие необходимые файлы находятся в папке %systemdrive%\drivertest\drivers. Устройство должно отображаться в диспетчер устройств без ошибок.

На хост-компьютере вы увидите это сообщение в окне Вывод .

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 ==========

Отладка приложения

  1. На хост-компьютере перейдите к x64 > Win8.1Debug в папке решения.

  2. Скопируйте исполняемый файл приложения UsbApplication1.exe на целевой компьютер.

  3. На целевом компьютере запустите приложение.

  4. На хост-компьютере в меню Отладка выберите Присоединить к процессу.

  5. В окне выберите Отладчик пользовательского режима Windows (средства отладки для Windows) в качестве транспорта и имя целевого компьютера, в данном случае dbg-target, в качестве квалификатора, как показано на этом рисунке.

    Параметр отладки шаблона winusb.

  6. Выберите приложение из списка Доступные процессы и выберите Присоединить. Теперь вы можете выполнять отладку с помощью окна Интерпретация или с помощью параметров в меню Отладка .

Предыдущие инструкции по отладке приложения с помощью средств отладки для Windows — удаленный отладчик. Если вы хотите использовать удаленный отладчик Windows (отладчик, который входит в состав Visual Studio), выполните следующие инструкции:

  1. На целевом компьютере добавьте msvsmon.exe в список приложений, разрешенных через брандмауэр.
  2. Запустите монитор удаленной отладки Visual Studio, расположенный в C:\DriverTest\msvsmon\msvsmon.exe.
  3. Создайте рабочую папку, например C:\remotetemp.
  4. Скопируйте исполняемый файл приложения UsbApplication1.exe в рабочую папку на целевом компьютере.
  5. На хост-компьютере в Visual Studio щелкните правой кнопкой мыши проект пакета USB Application1 и выберите пункт Выгрузить проект.
  6. Выберите и удерживайте (или щелкните правой кнопкой мыши) проект USB Application1 , в свойствах проекта разверните узел Свойства конфигурации и выберите Отладка.
  7. Измените отладчик для запуска на Удаленный отладчик Windows.
  8. Измените параметры проекта, чтобы запустить исполняемый файл на удаленном компьютере, следуя инструкциям, приведенным в статье Удаленная отладка локально созданного проекта. Убедитесь, что свойства Working Directory и Remote Command отражают папку на целевом компьютере.
  9. Чтобы выполнить отладку приложения, в меню Сборка выберите Начать отладку или нажмите клавишу F5.
  • Настройка одного компьютера.

    1. Чтобы выполнить сборку приложения и пакета установки драйвера, выберите Пункт Сборка решения в меню Сборка . Visual Studio отображает ход выполнения сборки в окне Вывода . (Если окно Вывод не отображается, выберите Вывод в меню Вид.) В этом упражнении мы создали проект для системы x64 под управлением Windows 10.

    2. Чтобы просмотреть встроенный пакет драйверов, перейдите в Windows Обозреватель в папку USB Application1, а затем перейдите к x64 > Debug > USB Application1 Package. Пакет драйверов содержит несколько файлов: MyDriver.inf — это информационный файл, который Windows использует при установке драйвера, mydriver.cat — файл каталога, который установщик использует для проверки тестовой подписи пакета драйвера. Эти файлы показаны на следующем снимке экрана.

      Шаблон приложения winusb.

      Файл драйвера отсутствует в пакете. Это связано с тем, что INF-файл ссылается на встроенный драйвер, Winusb.sys, который находится в папке Windows\System32.

    3. Установите драйвер вручную. В диспетчер устройств обновите драйвер, указав inf в пакете. Наведите указатель на пакет драйвера, расположенный в папке решения, как показано в предыдущем разделе. Если отображается сообщение об ошибке DriverVer set to a date in the future, задайте inf package project settings > Inf2Cat > General > Use Local Time > (Да).

    4. Выберите и удерживайте (или щелкните правой кнопкой мыши) проект USB Application1 , в свойствах проекта разверните узел Свойства конфигурации и выберите Отладка.

    5. Измените отладчик для запуска на Локальный отладчик Windows.

    6. Выберите и удерживайте (или щелкните правой кнопкой мыши) проект пакета USB Application1 и выберите Выгрузить проект.

    7. Чтобы выполнить отладку приложения, в меню Сборка выберите Начать отладку или нажмите клавишу F5.

Обсуждение кода шаблона

Шаблон является отправной точкой для классического приложения. Проект USB Application1 содержит исходные файлы device.cpp и main.cpp.

Файл main.cpp содержит точку входа приложения, _tmain. Device.cpp содержит все вспомогательные функции, которые открывают и закрывают дескриптор устройства.

Шаблон также содержит файл заголовка с именем device.h. Этот файл содержит определения GUID интерфейса устройства (рассматривается далее) и структуру DEVICE_DATA, в которой хранятся сведения, полученные приложением. Например, в нем хранится дескриптор интерфейса WinUSB, полученный OpenDevice и используемый в последующих операциях.

typedef struct _DEVICE_DATA {

    BOOL                    HandlesOpen;
    WINUSB_INTERFACE_HANDLE WinusbHandle;
    HANDLE                  DeviceHandle;
    TCHAR                   DevicePath[MAX_PATH];

} DEVICE_DATA, *PDEVICE_DATA;

Получение пути к экземпляру для устройства — см. раздел RetrieveDevicePath в device.cpp.

Для доступа к USB-устройству приложение создает допустимый дескриптор файла для устройства, вызвав CreateFile. Для этого вызова приложение должно получить экземпляр пути устройства. Чтобы получить путь к устройству, приложение использует подпрограммы SetupAPI и указывает GUID интерфейса устройства в INF-файле, который использовался для установки Winusb.sys. Device.h объявляет константу GUID с именем GUID_DEVINTERFACE_USBApplication1. С помощью этих подпрограмм приложение перечисляет все устройства в указанном классе интерфейса устройства и извлекает путь к устройству.

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

В предыдущей функции приложение получает путь к устройству, вызывая следующие подпрограммы:

  1. SetupDiGetClassDevs для получения дескриптора набора сведений об устройствах, массива, содержащего сведения обо всех установленных устройствах, соответствующих указанному классу интерфейса устройства, GUID_DEVINTERFACE_USBApplication1. Каждый элемент в массиве, называемый интерфейсом устройства , соответствует устройству, которое устанавливается и регистрируется в системе. Класс интерфейса устройства определяется путем передачи GUID интерфейса устройства, определенного в INF-файле. Функция возвращает дескриптор HDEVINFO в набор сведений об устройстве.

  2. SetupDiEnumDeviceInterfaces для перечисления интерфейсов устройств в наборе сведений об устройстве и получения сведений об интерфейсе устройства.

    Для этого вызова требуются следующие элементы:

    • Инициализированная структура, выделенная вызывающим объектом SP_DEVICE_INTERFACE_DATA , для которого для элемента cbSize задан размер структуры.

    • Дескриптор HDEVINFO из шага 1.

    • GUID интерфейса устройства, определенный в INF-файле.

      SetupDiEnumDeviceInterfaces ищет массив набора сведений об устройстве для указанного индекса интерфейса устройства и заполняет инициализированную структуру SP_DEVICE_INTERFACE_DATA основными данными об интерфейсе.

    Чтобы перечислить все интерфейсы устройств в наборе сведений об устройстве, вызывайте SetupDiEnumDeviceInterfaces в цикле, пока функция не вернет значение FALSE , а код ошибки для сбоя не будет ERROR_NO_MORE_ITEMS. Код ошибки ERROR_NO_MORE_ITEMS можно получить, вызвав Метод GetLastError. При каждой итерации увеличьте индекс элемента.

    Кроме того, можно вызвать метод SetupDiEnumDeviceInfo , который перечисляет набор сведений об устройстве и возвращает сведения об элементах интерфейса устройства, указанных индексом, в структуре, выделенной вызывающим объектом SP_DEVINFO_DATA . Затем можно передать ссылку на эту структуру в параметре DeviceInfoData функции SetupDiEnumDeviceInterfaces .

  3. SetupDiGetDeviceInterfaceDetail для получения подробных данных об интерфейсе устройства. Сведения возвращаются в структуре SP_DEVICE_INTERFACE_DETAIL_DATA . Поскольку размер структуры SP_DEVICE_INTERFACE_DETAIL_DATA отличается, setupDiGetDeviceInterfaceDetail вызывается дважды. Первый вызов получает размер буфера, выделяемый для структуры SP_DEVICE_INTERFACE_DETAIL_DATA . Второй вызов заполняет выделенный буфер подробными сведениями об интерфейсе.

    1. Вызывает метод SetupDiGetDeviceInterfaceDetail с параметром DeviceInterfaceDetailData , имеющим значение NULL. Функция возвращает правильный размер буфера в параметре requiredlength . Этот вызов завершается ошибкой с кодом ERROR_INSUFFICIENT_BUFFER. Этот код ошибки является ожидаемым.
    2. Выделяет память для структуры SP_DEVICE_INTERFACE_DETAIL_DATA на основе правильного размера буфера, полученного в параметре requiredlength .
    3. Снова вызывает setupDiGetDeviceInterfaceDetail и передает ему ссылку на инициализированную структуру в параметре DeviceInterfaceDetailData . При возврате функции структура заполняется подробными сведениями об интерфейсе. Путь к устройству находится в элементе DevicePath структуры SP_DEVICE_INTERFACE_DETAIL_DATA.

Создание дескриптора файла для устройства

См. раздел OpenDevice в device.cpp.

Для взаимодействия с устройством требуется дескриптор интерфейса WinUSB для первого (по умолчанию) интерфейса на устройстве. Код шаблона получает дескриптор файла и дескриптор интерфейса WinUSB и сохраняет их в 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;
}
  1. Приложение вызывает CreateFile , чтобы создать дескриптор файла для устройства, указав путь устройства, полученный ранее. Он использует флаг FILE_FLAG_OVERLAPPED, так как winUSB зависит от этого параметра.
  2. Используя дескриптор файла для устройства, приложение создает дескриптор интерфейса WinUSB. Функции WinUSB используют этот дескриптор для идентификации целевого устройства, а не дескриптора файла. Чтобы получить дескриптор интерфейса WinUSB, приложение вызывает WinUsb_Initialize путем передачи дескриптора файла. Используйте полученный дескриптор в последующих вызовах для получения сведений с устройства и отправки запросов ввода-вывода на устройство.

Освобождение дескрипторов устройства — см. раздел CloseDevice в device.cpp.

Код шаблона реализует код для освобождения дескриптора файла и дескриптора интерфейса WinUSB для устройства.

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

Дальнейшие действия

Затем ознакомьтесь со следующими разделами, чтобы отправить сведения об устройстве и передать данные на устройство: