如何从 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如下图:
然后传入该结构,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 适配器调用片段函数
如何从服务器获取和设置 android 中的 API(从服务器获取 int 值)?如何绑定和实现这个