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

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;
}

我正在使用Marshal.PtrToStructure方法处理WM_DEVICECHANGE消息的LParam参数。

这样做应该可以工作吗?

或者更好的方法是... 在连接时有没有其他的获取设备名称的方式?

编辑(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数组结束)。框架将获得指向一块内存的指针,其大小由第一个结构成员确定。除非有一种方法告诉marshaler这一点,否则我认为您必须使用本地代码来完成此操作(这就是我所做的)。 - Scott Smith
3个回答

11

正如上面提到的那样,我找到了让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;
}

谢谢!我一直在尝试做同样的事情。 - Andi Jay

2
这些信息也可以通过SetupAPI正式获取。使用dbcc_name传递给SetupDiOpenDeviceInterface,并使用SPDRP_FRIENDLYNAME传递SetupDiGetDeviceRegistryProperty以获取友好名称。

以下是一些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


有点晚了,已经有一个被接受的答案并获得了七个赞。 - LightningBoltϟ
6
永远为更好的答案而努力 =)(我宁愿选择正式的API而不是手动字符串解析) - Nathan Schubkegel
@NathanSchubkegel 如果你能找到人把这个翻译成C#,我会改变接受的答案。这将是绝对优先的解决方案,我发布的代码有点臭并且依赖于注册表中始终存在某些内容(或者注册表是否存在),WinAPI解决方案是最好的。 - snicker
这是用C#编写的吗? - AaA

0

你可能需要稍微修改一下

[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 StringBuilder dbcc_name;
}

dbcc_size 设置为 255,并按以下方式构造 StringBuilder:

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

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

编辑:snicker的评论之后...我想到了另一种方法...

public struct DEV_BROADCAST_DEVICEINTERFACE
{
    public int dbcc_size;
    public int dbcc_devicetype;
    public int dbcc_reserved;
    public Guid dbcc_classguid;
[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 255, ArraySubType = System.Runtime.InteropServices.UnmanagedType.LPArray)]
    public string dbcc_name;
}

dbcc_size设置为255,然后从那里开始...

编辑#2: 这很有趣...我现在不太确定了,我找到了这篇使用RegisterDeviceNotification的文章Codeproject ,它使用了一种不同的方法来注册设备通知,其中结构体被封送到一个IntPtr中并用于调用API...


不能转换为字符串构建器的字段。 这是无效的。 - snicker
汤姆,你的编辑还是不行。你只能将字符串转换为LPStr、LPWStr、LPTStr、BStr或ByValTStr。 - snicker
@Snicker:你尝试过更改属性“[System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr, SizeConst = 255, ArraySubType = System.Runtime.InteropServices.UnmanagedType.LPStr)]”了吗? - t0mm13b
问题不在于RegisterDeviceNotification。问题在于将消息封送到一个新结构体中。可能只是名称没有传递过来,但这似乎不太可能。 - snicker

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