You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 13:12:34