如何从 DEV_BROADCAST_DEVICEINTERFACE 和设备实例 ID 中获取友好的设备名称

Posted

技术标签:

【中文标题】如何从 DEV_BROADCAST_DEVICEINTERFACE 和设备实例 ID 中获取友好的设备名称【英文标题】:How to get friendly device name from DEV_BROADCAST_DEVICEINTERFACE and Device Instance ID 【发布时间】:2010-02-05 16:42:11 【问题描述】:

我已经用RegisterDeviceNotification 注册了一个窗口,并且可以成功接收DEV_BROADCAST_DEVICEINTERFACE 消息。但是,返回结构中的dbcc_name 字段始终为空。我的结构是这样定义的:

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_DEVICEINTERFACE

    public int dbcc_size;
    public int dbcc_devicetype;
    public int dbcc_reserved;
    public Guid dbcc_classguid;
    [MarshalAs(UnmanagedType.LPStr)]
    public string dbcc_name;

我在 WM_DEVICECHANGE 消息的 LParam 上使用了 Marshal.PtrToStructure

这应该有效吗?

甚至更好...有没有其他方法可以在连接时获取设备名称?

编辑(02/05/2010 20:56GMT):

我发现了如何通过这样做来填充 dbcc_name 字段:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct DEV_BROADCAST_DEVICEINTERFACE

    public int dbcc_size;
    public int dbcc_devicetype;
    public int dbcc_reserved;
    public Guid dbcc_classguid;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
    public string dbcc_name;

但我仍然需要一种从 int dbcc_name 中获取“友好”名称的方法。如下所示:

\?\USB#VID_05AC&PID_1294​​&MI_00#0#6bdd1fc6-810f-11d0-bec7-08002be2092f

我真的只想让它说“Apple iPhone”(在这种情况下就是设备)。

【问题讨论】:

我希望我错了,但在学习如何编组这些调用的过程中,我得出的结论是你不能编组DEV_BROADCAST_DEVICEINTERFACE ,因为它是一个可变大小的结构(以 ANYSIZE 数组结尾)。框架将获得一个指向内存块的指针,该内存块的大小由第一个结构成员决定。除非有办法告诉封送处理程序,否则我认为您必须在本机代码中执行此操作(这就是我所做的)。 【参考方案1】:

好吧,如上所述,我发现了如何正确填充 dbcc_name。我发现这是获取设备名称的最简单方法:

private static string GetDeviceName(DEV_BROADCAST_DEVICEINTERFACE dvi)

    string[] Parts = dvi.dbcc_name.Split('#');
    if (Parts.Length >= 3)
    
        string DevType = Parts[0].Substring(Parts[0].IndexOf(@"?\") + 2);
        string DeviceInstanceId = Parts[1];
        string DeviceUniqueID = Parts[2];
        string RegPath = @"SYSTEM\CurrentControlSet\Enum\" + DevType + "\\" + DeviceInstanceId + "\\" + DeviceUniqueID;
        RegistryKey key = Registry.LocalMachine.OpenSubKey(RegPath);
        if (key != null)
        
            object result = key.GetValue("FriendlyName");
            if (result != null)
                return result.ToString();
            result = key.GetValue("DeviceDesc");
            if (result != null)
                return result.ToString();
        
    
    return String.Empty;

【讨论】:

谢谢!!我一直在尝试做同样的事情。【参考方案2】:

也可以通过SetupAPI 更正式地获取此信息。将dbcc_name 传递给SetupDiOpenDeviceInterface 并通过SetupDiGetDeviceRegistryProperty 传入SPDRP_FRIENDLYNAME 获取友好名称。

这里有一些 Delphi 代码可以做到这一点。 (抱歉,您必须独立翻译成 C#)。

function ConvertDbccNameToFriendlyName(aDeviceInterfaceDbccName : string) : string;
var
  deviceInfoHandle : HDEVINFO;
  deviceInfoData : SP_DEVINFO_DATA;
  deviceInterfaceData : SP_DEVICE_INTERFACE_DATA;
  deviceInstanceId : string;
  memberIndex : Cardinal;
begin
  result := '';

  // Create a new empty "device info set"
  deviceInfoHandle := SetupDiCreateDeviceInfoList(nil, 0);
  if deviceInfoHandle <> INVALID_HANDLE_VALUE then
  begin
    try
      // Add "aDeviceInterfaceDbccName" to the device info set
      FillChar(deviceInterfaceData, SizeOf(deviceInterfaceData), 0);
      deviceInterfaceData.cbSize := SizeOf(deviceInterfaceData);
      if SetupDiOpenDeviceInterface(deviceInfoHandle, PChar(aDeviceInterfaceDbccName),     0, @deviceInterfaceData) then
      begin
        try
          // iterate over the device info set
          // (though I only expect it to contain one item)
          memberIndex := 0;
          while true do
          begin
            // get device info that corresponds to the next memberIndex
            FillChar(deviceInfoData, SizeOf(deviceInfoData), 0);
            deviceInfoData.cbSize := SizeOf(deviceInfoData);
            if not SetupDiEnumDeviceInfo(deviceInfoHandle, memberIndex, deviceInfoData) then
            begin
              // The enumerator is exhausted when SetupDiEnumDeviceInfo returns false
              break;
            end
            else
            begin
              Inc(memberIndex);
            end;

            // Get the friendly name for that device info
            if TryGetDeviceFriendlyName(deviceInfoHandle, deviceInfoData, out friendlyName) then
            begin
              result := friendlyName;
              break;
            end;
          end;
        finally
          SetupDiDeleteDeviceInterfaceData(deviceInfoHandle, deviceInterfaceData);
        end;
      end;
    finally
      SetupDiDestroyDeviceInfoList(deviceInfoHandle);
    end;
  end;
end;

function TryGetDeviceFriendlyName(
  var aDeviceInfoHandle : HDEVINFO;
  var aDeviceInfoData : SP_DEVINFO_DATA;
  out aFriendlyName : string) : boolean;
var
  valueBuffer : array of byte;
  regProperty : Cardinal;
  propertyRegDataType : DWord;
  friendlyNameByteSize : Cardinal;
  success : boolean;
begin
  aFriendlyName := '';
  result := false;

  // Get the size of the friendly device name
  regProperty := SPDRP_FRIENDLYNAME;
  friendlyNameByteSize := 0;
  SetupDiGetDeviceRegistryProperty(
    aDeviceInfoHandle,     // handle to device information set
    aDeviceInfoData,       // pointer to SP_DEVINFO_DATA structure
    regProperty,           // property to be retrieved
    propertyRegDataType,   // pointer to variable that receives the data type of the property
    nil,                   // pointer to PropertyBuffer that receives the property
    0,                     // size, in bytes, of the PropertyBuffer buffer.
    friendlyNameByteSize); // pointer to variable that receives the required size of PropertyBuffer

  // Prepare a buffer for the friendly device name (plus space for a null terminator)
  SetLength(valueBuffer, friendlyNameByteSize + sizeof(char));

  success := SetupDiGetDeviceRegistryProperty(
    aDeviceInfoHandle,
    aDeviceInfoData,
    regProperty,
    propertyRegDataType,
    @valueBuffer[0],
    friendlyNameByteSize,
    friendlyNameByteSize);

  if success then
  begin
    // Ensure that only 'friendlyNameByteSize' bytes are used.
    // Ensure that the string is null-terminated.
    PChar(@valueBuffer[friendlyNameByteSize])^ := char(0);

    // Get the returned value as a string
    aFriendlyName := StrPas(PChar(@valueBuffer[0]));
  end;

  result := success;
end;

最后...如果您需要一种唯一标识 USB 设备的方法(不是您要求的,但通常这也是需要的),请查看 SetupDiGetDeviceInstanceId

【讨论】:

回答有点晚了。一个答案已经以七票被接受。 获得更好的答案永远不会太迟 =)(我更喜欢正式的 API,而不是任何一天的手动字符串解析) @NathanSchubkegel 如果您能找到将其翻译成 C# 的人,我将更改已接受的答案。这将是绝对可取的解决方案,我上面发布的代码很臭,并且依赖于始终位于注册表(或根本存在的注册表)中相同位置的东西,最好使用 winapi 解决方案 这是在 C# 中吗?【参考方案3】:

你可能需要稍微改变一下

[StructLayout(LayoutKind.Sequential)] 公共结构 DEV_BROADCAST_DEVICEINTERFACE 公共 int dbcc_size; 公共 int dbcc_devicetype; 公共 int dbcc_reserved; 公共 Guid dbcc_classguid; [MarshalAs(UnmanagedType.LPStr)] 公共 StringBuilder dbcc_name;

设置dbcc_size为255,构造StringBuilder如下图:

DEV_BROADCAST_DEVICEINTERFACE dbd = 新的 DEV_BROADCAST_DEVICEINTERFACE; dbd.dbcc_size = 255; dbd.dbcc_name = new StringBuilder(dbd.dbcc_size);

然后传入该结构,dbcc_name 的值应该被填充。

编辑:窃笑的评论之后......我想到了另一种方式......

公共结构 DEV_BROADCAST_DEVICEINTERFACE 公共 int dbcc_size; 公共 int dbcc_devicetype; 公共 int dbcc_reserved; 公共 Guid dbcc_classguid; [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray,SizeConst = 255,ArraySubType = System.Runtime.InteropServices.UnmanagedType.LPArray)] 公共字符串 dbcc_name;

dbcc_size 设置为 255,然后从那里获取...

Edit#2:这很有趣...我现在不太确定,我发现这篇文章在 Codeproject 上使用了 RegisterDeviceNotification,它使用了不同的 RegisterDeviceNotification 方式,因为struct 被编组为IntPtr 并用于调用API...

【讨论】:

您不能封送属于 StringBuilder 的字段。这不起作用。 汤姆。您的编辑仍然无效。您只能将字符串编组为 LPStr、LPWStr、LPTStr、BStr 或 ByValTStr。 @Snicker: 你试过改变属性' [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr, SizeConst = 255, ArraySubType = System.Runtime.InteropServices.UnmanagedType .LPStr)]' 问题不在于RegisterDeviceNotification。问题是将消息编组到新结构中。虽然名字可能不会出现,但这似乎不太可能

以上是关于如何从 DEV_BROADCAST_DEVICEINTERFACE 和设备实例 ID 中获取友好的设备名称的主要内容,如果未能解决你的问题,请参考以下文章

如何将数据从回收器适配器发送到片段 |如何从 recyclerview 适配器调用片段函数

如何从 Firebase 获取所有设备令牌?

如何直接从类调用从接口继承的方法?

如何从服务器获取和设置 android 中的 API(从服务器获取 int 值)?如何绑定和实现这个

如何从Mac从android studio中的fabric注销? [复制]

如何从设备中获取 PDF 文件以便能够从我的应用程序中上传?