CreateFile 无法在 Windows 中打开 HID 设备

Posted

技术标签:

【中文标题】CreateFile 无法在 Windows 中打开 HID 设备【英文标题】:CreateFileA fails to open HID device in Windows 【发布时间】:2016-05-13 15:13:10 【问题描述】:

编辑:此处报告的问题:https://github.com/signal11/hidapi/issues/276

Inkling 是 Wacom 的一款笔设备。 (InklingReader) 是一个开源项目,可以从中获取实时数据。

我正在尝试整理 InklingReader 以使用 HIDAPI 而不是 libusb (因为它在更高级别上工作:HID 而不是原始 USB,因此更紧凑和更合适。libusb 也失败了最近的 OSX)。

HID API 一个小库:一个 .h,一个(每个平台).c。

我的代码如下所示:

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

在 Windows 上 hid_open 失败。单步显示故障点here:

// 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。” (here)

如果我列举 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 = Digitizers/Pen。

(编辑:有时第一个路径是 1/2,第二个是 13/2,其他时候它被交换)。

而 HIDAPI 是 only taking the first one it finds。

所以看起来这应该是解决方案。 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 只是引发了一个不同的错误:

usage_page== 1 => 错误代码 5 (ERROR_ACCESS_DENIED) usage_page==13 => 错误代码 32 (ERROR_SHARING_VIOLATION)

嗯。这是相当令人不安的。我好像走投无路了!

我试过摆弄 CreateFileA 的参数,例如用STANDARD_RIGHTS_READ | STANDARD_RIGHTS_WRITE 替换GENERIC_READ | GENERIC_WRITE——现在它愉快地创建了一个句柄。但是后续的hid_read-s 没有收集到任何数据。

谷歌搜索,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 似乎包含一些建议的解决方法:

toasterfirefly 都可以在 HID 堆栈中工作。烤面包机展示了如何 通过原始 PDO 寻址过滤器,萤火虫展示了如何访问 它与 WMI。从 C 的角度来看,我认为原始 PDO 要简单得多 要编码,WMI 有点讨厌和复杂。

fireflytoaster

作者推荐的是toaster中的东西,但它是一个很大的CodeBase,我没有Windows驱动程序编程的经验。

看起来我必须挖掘很多非常陌生的领域才能让任何事情发挥作用,所以在开始之前我在这里问。如果没有人回答并且我想通了,我会回答我自己的问题。

我唯一能想到的另一件事是,也许另一个进程已经在使用这条路径。也许如果我可以终止这个过程, CreateFileA 可能会成功? Roel 的 libusb 方法涉及分离内核驱动程序:https://github.com/roelj/inklingreader/blob/master/src/usb/online-mode.c#L98

PS 我在某个地方读到,如果另一个进程已经打开了这个设备,我们的打开必须与之前打开的权限相匹配。而且我还读到 Windows 在检测到时会自动打开所有 HID 设备。

Find out which process has an exclusive lock on a USB device handle

PPS 也许一个想法是尝试替代 HID 库 What is the best usb library to communicate with usb HID devices on Windows?

PPPS 也许我需要以管理员身份运行我的代码。但这不是一个好的解决方案。

【问题讨论】:

Windows 内置了数字化器感知功能,您肯定正在为平板电脑的使用而战。您可用的旋钮是禁用服务(应命名为“平板电脑输入服务”或“触控键盘和手写面板服务”或“TabletServiceWacom”)或禁用设备管理器中的“Wacom 虚拟 HID 设备”。使用原始输入可能会更好。 【参考方案1】:

我见过类似的行为。升级到 Windows 10 周年版后开始出现 ERROR_SHARING_VIOLATION 问题。只有在 Windows 启动时连接的 USB HID 设备才会出现此问题。如果在 Windows 启动后拔下并插入 USB 设备,则 CreateFile 成功。我还没有找到根本原因或解决方案。

【讨论】:

【参考方案2】:

你是对的:如果其他应用程序已经打开了这个设备,就会出现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 版本)已经打开此设备供其使用,则可能会失败。

关于键盘和鼠标设备的注意事项:你也可以在不设置desired_access(使用零值)的情况下调用::CreateFileW:在这种情况下你可以使用HidD_GetManufacturerString/HidD_GetProductString/ HidD_GetSerialNumberString/HidD_GetAttributes(可能还有其他一些)带有返回句柄的 HID 方法。但是您无法将数据读/写到此类设备。如果您需要获取 HID 键盘/鼠标的名称或 VID/PID,这应该很有用。

这里是list of HID device types and their access modes on Windows。

【讨论】:

以上是关于CreateFile 无法在 Windows 中打开 HID 设备的主要内容,如果未能解决你的问题,请参考以下文章

win32 api CreateFile 和 WriteFile 问题

无法通过 CreateFile 打开设备,ERROR_INVALID_FUNCTION

无法使用 CreateFile 函数打开文件

如何使 CreateFile 尽可能快

windows编程中的CreateFile 出现5的错误解决方法

windows编程中的CreateFile 出现5的错误解决方法