CreateFileA не удается открыть устройство HID в Windows

РЕДАКТИРОВАТЬ: О проблеме сообщили здесь: https://github.com/signal11/hidapi/issues/276

Inkling - это перьевое устройство от Wacom. ( InklingReader) - проект с открытым исходным кодом, который получает от него данные в реальном времени.

Я пытаюсь привести в порядок InklingReader для использования HIDAPI, а не libusb (так как он работает на более высоком уровне: HID, а не raw USB, поэтому он гораздо более компактен и удобен. Также libusb не работает на последних OSX).

HID API - небольшая библиотека: один.h, один (для каждой платформы).c.

Мой код выглядит так:

    unsigned short  inklingVendorId = 0x056a, inklingProductId = 0x0221;
    if (hid_init() == FAIL) return;   
    handle = hid_open(inklingVendorId, inklingProductId, nullptr);

На винде hid_open выходит из строя. Один шаг показывает здесь точку сбоя:

// path = "\\\\?\\hid#vid_056a&pid_0221&mi_00&col01#8&1ea90857&0&0000#"
//        "{4d1e55b2-f16f-11cf-88cb-001111000030}"
//
static HANDLE open_device(const char *path, BOOL enumerate)
{
    HANDLE handle;
    DWORD desired_access = (enumerate)? 0: (GENERIC_WRITE | GENERIC_READ);
    DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;

    // enumerate = 0
    handle = CreateFileA(path,
        desired_access,
        share_mode,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
        0);

    int err = GetLastError(); // 5 i.e. ERROR_ACCESS_DENIED

    return handle; // returns 0xffffffff i.e. INVALID_HANDLE
}

Теперь автор HIDAPI говорит: "HIDAPI не будет работать с клавиатурами и мышами в Windows. Windows в качестве меры безопасности не позволяет открывать HID для мыши и клавиатуры". ( здесь)

И если я перечислю HID устройства:

    struct hid_device_info *devs, *cur_dev;

    devs = hid_enumerate(inklingVendorId, inklingProductId);
    cur_dev = devs;
    while (cur_dev) {
        DBG2("Device Found\n  type: %04hx %04hx\n  path: %s\n  serial_number: %ls", cur_dev->vendor_id, cur_dev->product_id, cur_dev->path, cur_dev->serial_number);
        DBG2("");
        DBG2("  Manufacturer: %ls", cur_dev->manufacturer_string);
        DBG2("  Product:      %ls", cur_dev->product_string);
        DBG2("  Release:      %hx", cur_dev->release_number);
        DBG2("  Interface:    %d",  cur_dev->interface_number);
        DBG2("  Usage Page:   %d", cur_dev->usage_page);
        DBG2("  Usage:        %d", cur_dev->usage);
        DBG2("");
        cur_dev = cur_dev->next;
    }
    hid_free_enumeration(devs);

... я получаю не одну, а две записи:

Device Found
  type: 056a 0221
  path: \\?\hid#vid_056a&pid_0221&mi_00&col01#8&1ea90857&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
  serial_number: 2B0400001C90C22A0002DD07FE8B022A

  Manufacturer: Wacom, Inc.
  Product:      MSC Device
  Release:      1256
  Interface:    0
  Usage Page:   1
  Usage:        2

Device Found
  type: 056a 0221
  path: \\?\hid#vid_056a&pid_0221&mi_00&col02#8&1ea90857&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
  serial_number: 2B0400001C90C22A0002DD07FE8B022A

  Manufacturer: Wacom, Inc.
  Product:      MSC Device
  Release:      1256
  Interface:    0
  Usage Page:   13
  Usage:        2

(Примечание: OSX сообщает только о ВТОРОЙ записи! С OSX проблем нет!)

Сравнение path:
путь: \? \ hid # vid_056a & pid_0221 & mi_00 & col01 # 8 & 1ea90857 & 0 & 0000 #...
путь: \? \ hid # vid_056a & pid_0221 & mi_00 & col02 # 8 & 1ea90857 & 0 & 0001 #...

Согласно http://www.usb.org/developers/hidpage/Hut1_12v2.pdf,

UsagePage / Usage = 1/2 = {Общие элементы управления рабочим столом}/{Мышь}.
UsagePage / Usage = 13/2 = {Дигитайзеры}/{Ручка}.

(РЕДАКТИРОВАТЬ: иногда первый путь - 1/2, а второй - 13/2, в других случаях он поменяется местами).

И HIDAPI только берет первый, который находит.

Похоже, это должно быть решением. Inkling выставлял 2 "устройства", а hidapi выбирал неправильное (мышь), а Windows не разрешает доступ к мыши или клавиатурным устройствам.

Так что я поправляю код...

while (cur_dev) {
    if (cur_dev->vendor_id == vendor_id &&
        cur_dev->product_id == product_id &&
        cur_dev->usage_page == 13) 
    {

... чтобы получить правильную запись, она должна работать правильно?

Нет, CreateFileA просто вызывает другую ошибку:

Использование_страницы == 1 => Код ошибки 5 (ERROR_ACCESS_DENIED)
Использование_страницы ==13 => Код ошибки 32 (ERROR_SHARING_VIOLATION)

Мех. Это довольно расстраивает. Кажется, я в тупике!

Я попытался поиграться с параметрами CreateFileA, например, заменив GENERIC_READ | GENERIC_WRITE с STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE - теперь он счастливо создает ручку. Но последующее hid_read -S не в состоянии собирать какие-либо данные.

Поиск в Google, https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/af869f90-7bda-483d-ba2d-51680073fe9f/createfile-returns-invalid-handle-while-trying-to-access-hid-device-on-windows-8-desktop-app?forum=wdk while-tring-to-access-hid- устройство-на-windows-8-desktop-app? forum = wdk, кажется, содержит несколько предложенных обходных путей:

и тостер, и светлячок могут работать в стеке HID. Тостер показывает, как обращаться к фильтру через сырой PDO, Firefly показывает, как получить к нему доступ через WMI. С точки зрения C, я думаю, что сырой PDO гораздо проще кодировать, WMI немного неприятен и сложен.

Светляк
тостер

Автор рекомендует что-то в тостере, но это большая CodeBase, и у меня нет опыта программирования Windows Driver.

Похоже, мне придется копать много очень незнакомой территории, чтобы что-то заработало, поэтому перед началом я спрашиваю здесь. Если никто не ответит, и я пойму это, я отвечу на свой вопрос.

Единственное, о чем я могу думать, это то, что, возможно, другой путь уже идет по этому пути. Может быть, если я смогу завершить этот процесс, CreateFileA может быть успешным? Подход Roel к libusb включает в себя отключение драйвера ядра: https://github.com/roelj/inklingreader/blob/master/src/usb/online-mode.c#L98

PS Где-то я читал, что если другой процесс уже открыл это устройство, наше открытие должно соответствовать разрешениям этого предыдущего открытия. И я также читал, что Windows автоматически открывает все устройства HID при обнаружении.

Узнайте, какой процесс имеет эксклюзивную блокировку на ручке устройства USB

PPS, возможно, одна из идей - попробовать альтернативную библиотеку HID. Какая библиотека usb лучше всего подходит для связи с USB-устройствами HID в Windows?

PPPS, возможно, мне нужно запустить мой код от имени администратора. Но это не очень хорошее решение.

1 ответ

Я видел подобное поведение. Проблема ERROR_SHARING_VIOLATION начала возникать после обновления до Windows 10 Anniversary Edition. Проблема видна только для устройств USB HID, подключенных при запуске Windows. Если после запуска Windows отключить и подключить USB-устройство, CreateFile будет успешным. Я еще не нашел основную причину или решение.

Вы правы: ERROR_SHARING_VIOLATIONпроизойдет, если какое-то другое приложение уже открыло это устройство. Вам нужно вызвать CreateFileW API следующим образом:

DWORD desired_access = GENERIC_WRITE | GENERIC_READ;
DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
::CreateFileW(deviceInterfacePath, desired_access, share_mode, 0, OPEN_EXISTING, 0, 0);

Если вы не предоставите dwShareModeтогда это означает, что вы пытаетесь открыть исключительно устройство. Что может потерпеть неудачу, если другое приложение (новая версия Windows, которая, возможно, изначально поддерживает такие устройства) уже открыло это устройство для его использования.

Примечание об устройствах клавиатуры и мыши: вы также можете позвонить ::CreateFileW даже не установив desired_access (используйте нулевое значение): в этом случае вы можете использовать HidD_GetManufacturerString/HidD_GetProductString/HidD_GetSerialNumberString/HidD_GetAttributes(и, возможно, некоторые другие) методы HID с возвращенным дескриптором. Но вы не можете читать / записывать данные на такое устройство. Это может быть полезно, если вам нужно получить имя или VID/PID для HID клавиатуры / мыши.

Вот список типов HID-устройств и их режимов доступа в Windows.

Другие вопросы по тегам