如何在C#中获取原始输入设备的正确显示名称?
获取Windows原始输入设备的可读产品名称(WPF)
我正在开发一个WPF程序,目标是枚举当前连接到Windows的所有输入设备及其指针,点击窗口按钮时显示对应设备的人类可读名称(比如设备管理器里的「TUF GAMING M5」这类名称)。
已完成的设备枚举代码
目前已经实现了收集所有输入设备的逻辑:
NativeMethods.GetRawInputDeviceList(pRawInputDeviceList, ref deviceCount, (uint)dwSize); for (var i = 0; i < deviceCount; i++) { var rid = (RawInputDeviceList)Marshal.PtrToStructure(new IntPtr((pRawInputDeviceList.ToInt64() + (dwSize * i))), typeof(RawInputDeviceList)); }
当前问题
- 使用
NativeMethods.GetRawInputDeviceInfo(hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize);只能获取设备路径,无法得到需要的可读名称。 - 了解到
HidD_GetProductString函数,但有说法称它仅适用于HID设备,对鼠标/键盘不适用;同时也不知道如何通过原始输入设备结构体获取合适的BufferLength参数。
解决方案:通过SetupAPI查询设备友好名称
要获取设备管理器中显示的标准名称,更可靠的方式是结合设备路径,调用Windows SetupAPI读取注册表中的设备属性。以下是具体实现步骤:
1. 从原始输入设备获取设备路径
先通过GetRawInputDeviceInfo拿到设备的路径字符串:
uint pcbSize = 0; // 先获取存储设备路径所需的缓冲区大小 NativeMethods.GetRawInputDeviceInfo(rid.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, IntPtr.Zero, ref pcbSize); IntPtr devicePathPtr = Marshal.AllocHGlobal((int)pcbSize); try { if (NativeMethods.GetRawInputDeviceInfo(rid.hDevice, RawInputDeviceInfo.RIDI_DEVICENAME, devicePathPtr, ref pcbSize) != -1) { string devicePath = Marshal.PtrToStringUni(devicePathPtr); // 调用函数获取友好名称 string deviceFriendlyName = GetDeviceFriendlyName(devicePath); // 这里可以将deviceFriendlyName关联到对应的设备 } } finally { Marshal.FreeHGlobal(devicePathPtr); }
2. 实现获取设备友好名称的核心函数
通过SetupAPI解析设备路径,读取注册表中的友好名称或设备描述:
// 导入SetupAPI相关函数 [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool SetupDiOpenDeviceInfo(IntPtr DeviceInfoSet, string DeviceInstanceId, IntPtr hwndParent, uint Flags, ref SP_DEVINFO_DATA DeviceInfoData); [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, uint Property, out uint PropertyRegDataType, IntPtr PropertyBuffer, uint PropertyBufferSize, out uint RequiredSize); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); // 常量定义 private const uint DIGCF_ALLCLASSES = 0x00000004; private const uint SPDRP_FRIENDLYNAME = 0x0000000C; // 友好名称属性 private const uint SPDRP_DEVICEDESC = 0x00000000; // 设备描述属性(备用) // SP_DEVINFO_DATA结构体 [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { public uint cbSize; public Guid ClassGuid; public uint DevInst; public IntPtr Reserved; } private string GetDeviceFriendlyName(string devicePath) { // 从设备路径中提取设备实例ID string deviceInstanceId = ExtractDeviceInstanceId(devicePath); if (string.IsNullOrEmpty(deviceInstanceId)) return "未知设备"; Guid emptyGuid = Guid.Empty; // 获取所有类别的设备信息集合 IntPtr deviceInfoSet = SetupDiGetClassDevs(ref emptyGuid, null, IntPtr.Zero, DIGCF_ALLCLASSES); if (deviceInfoSet == IntPtr.Zero) return "未知设备"; try { SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); deviceInfoData.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA)); // 打开指定实例ID的设备信息 if (SetupDiOpenDeviceInfo(deviceInfoSet, deviceInstanceId, IntPtr.Zero, 0, ref deviceInfoData)) { uint requiredSize = 0; // 先获取存储属性所需的缓冲区大小 SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME, out _, IntPtr.Zero, 0, out requiredSize); if (requiredSize > 0) { IntPtr buffer = Marshal.AllocHGlobal((int)requiredSize); try { // 尝试读取友好名称 if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME, out _, buffer, requiredSize, out _)) { return Marshal.PtrToStringUni(buffer); } // 如果友好名称不存在,读取设备描述作为备选 else { SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_DEVICEDESC, out _, buffer, requiredSize, out _); return Marshal.PtrToStringUni(buffer); } } finally { Marshal.FreeHGlobal(buffer); } } } return "未知设备"; } finally { // 释放设备信息集合 SetupDiDestroyDeviceInfoList(deviceInfoSet); } } // 从设备路径中提取设备实例ID的辅助函数 private string ExtractDeviceInstanceId(string devicePath) { // 设备路径格式示例:\\?\HID#VID_0B05&PID_1837&MI_00#8&35a7d20&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd} // 提取两个#之间的部分作为设备实例ID int firstHashIndex = devicePath.IndexOf('#'); if (firstHashIndex == -1) return null; int secondHashIndex = devicePath.IndexOf('#', firstHashIndex + 1); if (secondHashIndex == -1) return null; return devicePath.Substring(firstHashIndex + 1, secondHashIndex - firstHashIndex - 1); }
3. 关于HidD_GetProductString的补充说明
HidD_GetProductString确实仅针对HID类设备(如游戏手柄、手写板等),对于标准鼠标、键盘这类设备,Windows通常不会通过HID报告提供产品字符串,因此使用SetupAPI的方式更通用,能覆盖所有类型的输入设备。
内容的提问来源于stack exchange,提问作者The Great ReLLeRtR
相关产品推荐
相关产品推荐

