How to get friendly device name from DEV_BROADCAST_DEVICEINTERFACE and Device Instance ID
Asked Answered
S

4

11

I've registered a window with RegisterDeviceNotification and can successfully recieve DEV_BROADCAST_DEVICEINTERFACE messages. However, the dbcc_name field in the returned struct is always empty. The struct I have is defined as such:

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

And I'm using Marshal.PtrToStructure on the LParam of the WM_DEVICECHANGE message.

Should this be working?

Or even better... Is there an alternative way to get the name of a device upon connection?

EDIT (02/05/2010 20:56GMT):

I found out how to get the dbcc_name field to populate by doing this:

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

but I still need a way to get a "Friendly" name from what is int dbcc_name. It looks like the following:

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

And I really just want it to say "Apple iPhone" (which is what the device is in this case).

Sooth answered 5/2, 2010 at 16:42 Comment(1)
I hope I'm wrong about this, but in the process of learning how to marshal these calls, I came to the conclusion that you can't marshal DEV_BROADCAST_DEVICEINTERFACE, because it's a variable size struct (ends with an ANYSIZE array). The framework will get a pointer to a chunk of memory whose size is determined by the first struct member. Unless there's a way to tell the marshaler about that, I think you have to do this in native code (that's what I did).Geraldine
S
11

Well, as noted above I found out how to get dbcc_name to populate correctly. I found that this was the easiest way to get the device 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;
}
Sooth answered 8/2, 2010 at 17:20 Comment(1)
THANKS!! I've been trying to do the same thing.Tonita
L
2

This information can also be acquired more formally through SetupAPI. Pass dbcc_name to SetupDiOpenDeviceInterface and get the friendly name with SetupDiGetDeviceRegistryProperty passing in SPDRP_FRIENDLYNAME.

Here's some Delphi code that will do it. (Sorry, you'll have to translate to C# independently).

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;

Finally... if you need a way to uniquely identify a USB device (not what you asked for, but commonly this is also needed), look into SetupDiGetDeviceInstanceId.

Layton answered 11/12, 2013 at 15:33 Comment(4)
A bit late for answering. An answer has already been accepted with seven votes.Paradise
It's never too late for a better answer =) (I prefer a formal API over manual string parsing any day)Layton
@NathanSchubkegel if you can find someone to translate this to C#, I'll change the accepted answer. This would be the absolutely preferable solution, the code I posted above is smelly and relies on something always being in the same location in the registry (or the registry existing at all), a winapi solution is bestSooth
So is this in C#?Alphitomancy
M
0

It is likely you need to change this slightly

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

Set the dbcc_size to 255, and construct the StringBuilder as shown below:

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

Then pass that structure in, the value of dbcc_name should be populated.

Edit: after snicker's comment...I thought of this another way...

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

Set the dbcc_size to 255, and take it from there...

Edit#2: This is interesting...am not so sure now, I found this article that uses RegisterDeviceNotification on Codeproject and it uses a different way of RegisterDeviceNotification in that the struct is marshalled into a IntPtr and is used to call the API...

Myrtlemyrvyn answered 5/2, 2010 at 16:49 Comment(4)
You cannot marshal fields that are StringBuilders. This doesn't work.Sooth
Tom. Your edit still doesn't work. You can only marshal strings to LPStr, LPWStr, LPTStr, BStr, or ByValTStr.Sooth
@Snicker: have you tried changing the attribute ' [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.LPStr, SizeConst = 255, ArraySubType = System.Runtime.InteropServices.UnmanagedType.LPStr)]'Myrtlemyrvyn
The problem is not with RegisterDeviceNotification. The problem is marshaling a message into a new struct. It's possible the names just aren't coming though, but that seems unlikelySooth
M
0

Parsing device interface of device instance is not a good idea.

DEVPKEY_NAME is a best query you should do.

First, you have to convert device interface name to device instance id.

Something like this with SetupAPI:

void GetDeviceInstanceId(const wchar_t* device_interface, wchar_t* instance_id, size_t len)
{
  HDEVINFO devinfo;
  SP_DEVICE_INTERFACE_DATA interface_data;
  SP_DEVINFO_DATA device_data;
  
  devinfo = SetupDiCreateDeviceInfoList(NULL, NULL);
  
  memset(interface_data, 0, sizeof(interface_data));
  interface_data.cbSize = sizeof(interface_data);
  SetupDiOpenDeviceInterface(&devinfo, device_interface, 0, interface_data);
  
  memset(device_data, 0, sizeof(device_data));
  device_data.cbSize = sizeof(device_data);
  SetupDiGetDeviceInterfaceDetail(devinfo, interface_data, NULL, 0, NULL, device_data);
  
  SetupDiGetDeviceProperty(devinfo, device_data, DEVPKEY_Device_InstanceId, DEVPROP_TYPE_STRING, instance_id, len);
  // alternatively:
  //SetupDiGetDeviceInstanceId(devinfo, device_data, instance_id, len);

  SetupDiDeleteDeviceInterfaceData(devinfo, interface_data);
  SetupDiDestroyDeviceInfoList(devinfo);
}

void GetDeviceName(const wchar_t *instance_id, wchar_t *name, size_t len)
{
  HDEVINFO devinfo;
  SP_DEVINFO_DATA device_data;
  
  memset(device_data, 0, sizeof(device_data));
  device_data.cbSize = sizeof(device_data);
  SetupDiOpenDeviceInfo(devinfo, instance_id, NULL, 0, device_data);

  SetupDiGetDeviceProperty(devinfo, &device_data, DEVPKEY_NAME, DEVPROP_TYPE_STRING, name, len, 0);
  
  SetupDiDestroyDeviceInfoList(devinfo);
}

Or with more modern cfgmgr32 API:

void GetDeviceInstanceId(const wchar_t* device_interface, wchar_t *buffer, size_t len)
{
  DEVPROPTYPE type = DEVPROP_TYPE_STRING;

  CM_Get_Device_Interface_Property(device_interface, &DEVPKEY_Device_InstanceId, &type, (PBYTE)buffer, len, 0); // CR_SUCCESS
}

void GetDeviceName(const wchar_t *instance_id, wchar_t *buffer, size_t len)
{
  DEVINST device;
  DEVPROPTYPE type = DEVPROP_TYPE_STRING;
  
  CM_Locate_DevNode(&device, instance_id, 0); // CR_SUCCESS
  CM_Get_DevNode_Property(device, &DEVPKEY_NAME, &type, (PBYTE)buffer, len, 0); // CR_SUCCESS
}

Mccutcheon answered 9/7, 2024 at 21:46 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.