如何在Windows中从USB HID设备获取原始描述符数据?

7
如何从 Windows 中的 HID 设备获取原始描述符数据?
背景:
我需要从 Windows 的 HID 设备中获取制造商、产品名称和序列号。我正在使用 hid.dll 使用此处看到的函数访问设备。我的问题与此问题非常相似。我能够从一些 HID 设备中获取制造商字符串和产品字符串,但大多数设备无法返回此数据,HidD_GetManufacturerString 返回 false。然而,我知道这些设备在它们的描述符中有字符串信息,因为我能够使用 USBTreeView 查看
有趣的是,即使对于返回制造商和产品名称的设备,我通过 hid.dll 得到的值与使用上述工具从 USB 设备获取的原始数据非常不同。
例如,Xbox 360 控制器:
Via USB Tree View:
Device Description       : Xbox 360 Controller for Windows
Language 0x0409          : "©Microsoft Corporation"
iProduct                 : 0x02
Language 0x0409          : "Controller"
iSerialNumber            : 0x03
Language 0x0409          : "0843806"

Via hid.dll using HidD_GetManufacturerString, HidD_GetProductString, and HidD_GetSerialNumberString:
Description              : HID-compliant game controller
Product                  : Controller (XBOX 360 Controller for Windows)
Manufacturer             : FAILS
Serial Number            : FAILS

WinUSB无法打开这些设备以检索数据,因为它们不使用winusb.sys驱动程序。

1)我不明白为什么HidD函数返回的值与USB描述符中的值不匹配。 2)我找不到任何访问HID设备的原始USB描述符数据的方法,因为我无法使用WinUSB访问它们。


编辑1:

好的,我对HID有了更多的了解。看起来我通过hid.dll获取的数据是驱动程序指定的数据,而不是来自USB设备的数据。HID也可以应用于除USB之外的其他传输设备。所以这很好。最终,我真正想知道的是,在我拥有HID设备时如何获取USB设备,以及我要使用哪个API。除了不起作用的WinUSB之外,我能找到的唯一东西就是内核级函数IOCTL。我不知道这是否适用于普通的非管理员应用程序。


认为USB设备将是HID设备的父级(至少在我的系统中在设备管理器中是这样显示的),因此也许您可以使用CM_Get_Parent和相关函数? - Harry Johnston
@HarryJohnston 感谢您的提示。不幸的是,父设备并不总是USB设备,因为一些HID设备嵌套了几个级别。最终,关键是CM函数。我会添加一个答案。 - Guavaman
2个回答

2
我终于找到了解决方案。主要问题就是将HID设备与其父USB设备关联起来。以下是基本的过程:
假设你已经有了HID设备和它的SP_DEVINFO_DATA:
1. 枚举所有的USB设备(如此处所示)。 2. 使用CM_GetChild和CM_GetSibling找到USB设备的所有子项。 3. 将已知的HID设备实例句柄(SP_DEVINFO_DATA->DevInst)与CM函数返回的每个子设备实例句柄进行比较,以确定哪个USB设备是���设备。 4. 从那里,您可以获取任何USB信息,包括描述符。

你不能反复调用CM_Get_Parent来跟踪链路直到找到一个USB设备吗? - Harry Johnston
@HarryJohnston 你可能是对的。我没有考虑到这一点,主要是因为在经历了整个过程之后,我才真正理解了USB设备和HID设备之间的关系。你还需要能够识别哪个是最上层的USB设备,因为可能会有多个设备。我猜想当你遇到一个集线器时,你会想要停止并返回-1。 - Guavaman
从那里,你可以获取任何你想要的USB信息,包括描述符。我正试图弄清楚如何做到这一点。当您说父USB设备时,是否意味着HID连接的Hub?因为即使我有USB设备实例句柄和设备路径,我也无法获取USB描述符。似乎您需要调用DeviceIoControl传递hub,并指定连接设备的端口,以便获取描述符。是否有其他方法可以仅通过USB设备实例句柄获取它? - Malaise
@Malaise:当你有一个 SP_DEVINFO_DATA 时,你可以使用 SetupDiGetDeviceProperty 调用来获取信息。例如,制造商对应于 DEVPKEY_Device_Manufacturer - Ben Voigt

0

我在这里做了这件事。有很多需要记住的事情,比如复合USB设备支持。主要算法大致如下:

std::string usbHubInterface;
std::string compositeDeviceInstanceId;

for (std::string deviceInstanceId = GetParentDevice(hidDeviceInstanceId); !deviceInstanceId.empty(); deviceInstanceId = GetParentDevice(deviceInstanceId))
{
    std::string usbDeviceInterface = GetDeviceInterface(deviceInstanceId, &GUID_DEVINTERFACE_USB_DEVICE);
    if (!usbDeviceInterface.empty())
    {
        m_DeviceInterfacePath = usbDeviceInterface;
    }

    std::string usbHub = GetDeviceInterface(deviceInstanceId, &GUID_DEVINTERFACE_USB_HUB);
    if (!usbHub.empty())
    {
        usbHubInterface = usbHub;
        break;
    }

    // May be composite USB device. Save it for later use.
    if (usbDeviceInterface.empty()) 
    {
        compositeDeviceInstanceId = deviceInstanceId;
    }
}

if (usbHubInterface.empty())
{
    DBGPRINT("UsbDevice: cannot get parent USB hub interface");
    return;
}

if (!m_DeviceInterfacePath.empty())
{
    m_DeviceInstanceId = GetDeviceFromInterface(m_DeviceInterfacePath);

    DEVINST devNodeHandle = OpenDevNode(m_DeviceInstanceId);

    // Get device index in parent USB hub
    // https://learn.microsoft.com/windows-hardware/drivers/ddi/wdm/ns-wdm-_device_capabilities#usb
    m_UsbPortIndex = PropertyDataCast<ULONG>(GetDevNodeProperty(devNodeHandle, &DEVPKEY_Device_Address, DEVPROP_TYPE_UINT32));

    // Composite USB device
    if (IsCompositeUSBDevice(m_DeviceInstanceId))
    {
        // Need to acquire interface number in parent USB device
        // https://learn.microsoft.com/windows-hardware/drivers/usbcon/usb-common-class-generic-parent-driver
        if (!GetInterfaceNumber(compositeDeviceInstanceId, m_UsbInterfaceNumber))
        {
            DBGPRINT("UsbDevice: cannot get interface number from composite USB device");
            return;
        }
    }
}
else
{
    DBGPRINT("UsbDevice: cannot get parent USB device interface");
    return;
}

// Open device handle first to wake up device from S3 suspend state
ScopedHandle usbInterfaceHandle = OpenDeviceInterface(m_DeviceInterfacePath);
ScopedHandle hubInterfaceHandle = OpenDeviceInterface(usbHubInterface, true);

USB_DEVICE_DESCRIPTOR deviceDescriptor;
if (!GetDeviceDescriptor(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor))
    return;

m_VendorId = deviceDescriptor.idVendor;
m_ProductId = deviceDescriptor.idProduct;
m_VersionNumber = deviceDescriptor.bcdDevice;

// Assume that we are always using first configuration
const UCHAR configurationIndex = 0;
if (!GetFullConfigurationDescriptor(hubInterfaceHandle, m_UsbPortIndex, configurationIndex, m_ConfigurationDescriptor))
    return;

// Search for interface descriptor
PUSB_INTERFACE_DESCRIPTOR interfaceDescriptor = nullptr;
if (!SearchInterfaceDescriptor(m_ConfigurationDescriptor, m_UsbInterfaceNumber, interfaceDescriptor))
    return;

std::wstring stringBuffer;
// Get the array of supported Language IDs, which is returned in String Descriptor 0
if (!GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, 0, 0, stringBuffer))
    return;

// Use first supported language
USHORT languageID = stringBuffer[0];
if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor.iManufacturer, languageID, stringBuffer))
    m_Manufacturer = utf8::narrow(stringBuffer);

// Get interface name instead of whole product name, if present
UCHAR productStringIndex = interfaceDescriptor->iInterface ? interfaceDescriptor->iInterface : deviceDescriptor.iProduct;
if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, productStringIndex, languageID, stringBuffer))
    m_Product = utf8::narrow(stringBuffer);

if (GetDeviceString(hubInterfaceHandle, m_UsbPortIndex, deviceDescriptor.iSerialNumber, languageID, stringBuffer))
    m_SerialNumber = utf8::narrow(stringBuffer);

// Get HID Descriptor
PHID_DESCRIPTOR hidDescriptor = nullptr;
if (!GetHidDescriptor(interfaceDescriptor, hidDescriptor))
    return;

// Get raw HID Report Descriptor
if (!GetHidReportDescriptor(hubInterfaceHandle, m_UsbPortIndex, hidDescriptor->DescriptorList[0].wReportLength, interfaceDescriptor->bInterfaceNumber, m_HidReportDescriptor))
{
    DBGPRINT("UsbDevice: cannot get raw HID Report Descriptor");
}

对于标准描述符,您应该能够跳过所有这些步骤,直接调用 SetupDiGetDeviceProperty。例如 DEVPKEY_Device_Manufacturer - Ben Voigt
同意。但是这段代码是为了尝试获取USB_INTERFACE_DESCRIPTORHID_DESCRIPTOR而编写的。 - DJm00n

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接