Windows开发过程中,有时候需要进行设备信息的管理,那么SetupDi系列函数能够满足你的要求。
枚举设备的相关文章,相关的章节和函数列举如下:
Device Information Sets :https://learn.microsoft.com/en-us/windows-hardware/drivers/install/device-information-sets
Device Interface Classes :https://learn.microsoft.com/en-us/windows-hardware/drivers/install/overview-of-device-interface-classes
Setupapi :https://learn.microsoft.com/zh-cn/windows/win32/api/setupapi/
UsbTreeView(进行USB设备信息管理时,强烈建议下载一个UsbTreeView ,开发时有很大的帮助)。以下是通过 usb device tree viewer 看到的结构图:
HDEVINFO SetupDiGetClassDevs(
_In_opt_ const GUID *ClassGuid,
_In_opt_ PCTSTR Enumerator,
_In_opt_ HWND hwndParent,
_In_ DWORD Flags
);
SetupDiGetClassDevs
获取一个指定类别或全部类别的所有已安装设备的信息。
ClassGuid
: 一个特定类别GUID(需要查询注册表)的指针,如果设置了 DIGCF_ALLCLASSES
标记,该参数备忽略,将返回所有类别的设备信息表(这里的GUID有设备安装类的GUID和设备接口类的GUID)。
常用设备接口类GUID
Identifier | Class GUID | Header |
---|---|---|
GUID_DEVINTERFACE_USB_DEVICE | {A5DCBF10-6530-11D2-901F-00C04FB951ED} | Usbiodef.h |
GUID_DEVINTERFACE_USB_HOST_CONTROLLER | {3ABF6F2D-71C4-462A-8A92-1E6861E6AF27} | Usbiodef.h |
GUID_DEVINTERFACE_USB_HUB | {F18A0E88-C30C-11D0-8815-00A0C906BED8} | Usbiodef.h |
GUID_DEVINTERFACE_NET | {CAC88484-7515-4C03-82E6-71A87ABAC361} | Ndisguid.h |
GUID_DEVINTERFACE_MODEM | {2C7089AA-2E0E-11D1-B114-00C04FC2AAE4} | Ntddmodm.h |
GUID_DEVINTERFACE_DISK | {53F56307-B6BF-11D0-94F2-00A0C91EFB8B} | Ntddstor.h |
GUID_DEVINTERFACE_VOLUME | {53F5630D-B6BF-11D0-94F2-00A0C91EFB8B} | Ntddstor.h |
GUID_DEVINTERFACE_MEDIUMCHANGER | {53F56310-B6BF-11D0-94F2-00A0C91EFB8B} | Ntddstor.h |
GUID_DEVINTERFACE_CDROM | {53F56308-B6BF-11D0-94F2-00A0C91EFB8B} | Ntddstor.h |
GUID_DEVINTERFACE_PARTITION | {53F5630A-B6BF-11D0-94F2-00A0C91EFB8B} | Ntddstor.h |
GUID_DEVINTERFACE_HID | {4D1E55B2-F16F-11CF-88CB-001111000030} | Hidclass.h |
GUID_DEVINTERFACE_KEYBOARD | {884B96C3-56EF-11D1-BC8C-00A0C91405DD} | Ntddkbd.h |
GUID_DEVINTERFACE_MOUSE | {378DE44C-56EF-11D1-BC8C-00A0C91405DD} | Ntddmou.h |
GUID_DEVINTERFACE_DISPLAY_ADAPTER | {5B45201D-F2F2-4F3B-85BB-30FF1F953599} | Ntddvdeo.h |
GUID_DEVINTERFACE_IMAGE | {6BDD1FC6-810F-11D0-BEC7-08002BE2092F} | Wiaintfc.h |
GUID_DEVINTERFACE_MONITOR | {E6F07B5F-EE97-4a90-B076-33F57BF4EAA7} | Ntddvdeo.h |
GUID_DEVINTERFACE_BRIGHTNESS | {FDE5BBA4-B3F9-46FB-BDAA-0728CE3100B4} | Dispmprt.h |
GUID_DEVINTERFACE_I2C | {2564AA4F-DDDB-4495-B497-6AD4A84163D7} | Dispmprt.h |
GUID_BTHPORT_DEVICE_INTERFACE | {0850302A-B344-4fda-9BE9-90576B8D46F0} | Bthdef.h |
GUID_DEVINTERFACE_COMPORT | {86E0D1E0-8089-11D0-9CE4-08003E301F73} | Ntddser.h |
GUID_DEVINTERFACE_PARCLASS | {811FC6A5-F728-11D0-A537-0000F8753ED1} | Ntddpar.h |
Enumerator
: 过滤枚举的内容:如:PCI则只显示PCI设备
hwndParent
: 用于关联到集合成员中的用户接口的顶层窗口句柄
Flags
: 建立设备信息表的控制选项,可以是下列值
DIGCF_ALLCLASSES
: 范围最广,对全部的任何设备类或接口类有支持的设备;DIGCF_DEVICEINTERFACE
: 仅包含支持设备接口类的设备;DIGCF_DEFAULT
: 仅包含支持默认接口类的设备;DIGCF_PRESENT
: 当前连接的设备;DIGCF_PROFILE
: Hardware Profile中的设备,要看哪些设备在硬件Profile中,应到注册表键HKLM/SYSTEM/CurrentControlSet/Hardware Profiles/Current/System/CurrentControlSet
下查看;如成功,返回包含所有与指定参数匹配的已经安装设备信息句柄
如失败则返回INVALID_HANDLE_VALUE
主要是从HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum
(如果是接口类,那么通过HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
获取)枚举到所有的信息,然后获取到ClassGUID,然后根据这个值过滤参数。其中,这里主要根据Flags的不同,然后枚举不同的注册表。
只有当Flags指定为DIGCF_DEVICEINTERFACE
,才会从HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
读取相关信息。
过滤到结果之后,将结果放入到一个全局链表DeviceInfoSet
中,提供给后续的函数遍历。
可以遍历两种类别:
1. Device Setup Class Control Options
2. Device Interface Class Control Options
其中 DeviceInfoSet( HDEVINFO )
结构如下,有一个LIST_ENTRY ListHead
;如下:
struct DeviceInfoSet /* HDEVINFO */
{
DWORD magic; /* SETUP_DEVICE_INFO_SET_MAGIC */
GUID ClassGuid;
HKEY HKLM;
HMACHINE hMachine;
SP_DEVINSTALL_PARAMS_W InstallParams;
LIST_ENTRY DriverListHead;
LIST_ENTRY ListHead; //DeviceInfo
struct DeviceInfo *SelectedDevice;
struct ClassInstallParams ClassInstallParams;
HMODULE hmodClassPropPageProvider;
PVOID pClassPropPageProvider;
PCWSTR MachineName;
WCHAR szData[ANYSIZE_ARRAY];
};
DeviceInfo的结构信息如下:
struct DeviceInfo /* Element of DeviceInfoSet.ListHead */
{
LIST_ENTRY ListEntry;
DEVINST dnDevInst;
struct DeviceInfoSet *set;
SP_DEVINSTALL_PARAMS_W InstallParams;
PCWSTR instanceId;
PCWSTR UniqueId;
PCWSTR DeviceDescription;
GUID ClassGuid;
DWORD CreationFlags;
LIST_ENTRY DriverListHead; /* List of struct DriverInfoElement */
LIST_ENTRY InterfaceListHead; /* List of struct DeviceInterface */
struct ClassInstallParams ClassInstallParams;
HMODULE hmodDevicePropPageProvider;
PVOID pDevicePropPageProvider;
WCHAR Data[ANYSIZE_ARRAY];
};
DeviceInterface 设备接口的结构信息如下:
struct DeviceInterface /* Element of DeviceInfo.InterfaceListHead */
{
LIST_ENTRY ListEntry;
struct DeviceInfo *DeviceInfo;
GUID InterfaceClassGuid;
DWORD Flags;
WCHAR SymbolicLink[ANYSIZE_ARRAY];
};
SetupDiGetClassDevs
获取设备信息的集合,通过如下两个接口也可以达到同样的目的(其实SetupDiGetClassDevs
内部也是通过这个函数来实现的):
SetupDiCreateDeviceInfoList : 创建设备集合
SetupDiCreateDeviceInfo : 创建设备信息
SetupDiEnumDeviceInfo
枚举指定设备信息集合的成员,并将数据放在PSP_DEVINFO_DATA
中
BOOL SetupDiEnumDeviceInfo(
_In_ HDEVINFO DeviceInfoSet,
_In_ DWORD MemberIndex,
_Out_ PSP_DEVINFO_DATA DeviceInfoData
);
DeviceInfoSet
: 提供一个设备信息集合的句柄
MemberIndex
: 指定一个要取得的设备信息成员序号,从0开始
DeviceInfoData
: 指向SP_DEVINFO_DATA
结构的指针,关于指定成员的返回信息就放在该结构中
typedef struct _SP_DEVINFO_DATA {
DWORD cbSize;
GUID ClassGuid;
DWORD DevInst;
ULONG_PTR Reserved;
} SP_DEVINFO_DATA, *PSP_DEVINFO_DATA;
如果要枚举全部设备信息成员,装载者首先应该将MemberIndex
设为0调用SetupDiEnumDeviceInfo
,然后递增MemberIndex
(使用一个for循环),调用SetupDiEnumDeviceInfo
,直至所有成员全部遍历(此时函数返回False,并且GetLastError返回ERROR_NO_MORE_ITEMS)。
枚举DeviceInfoSet
链表中的元素。
SetupDiEnumDeviceInterfaces
枚举枚举集合中的设备接口
WINSETUPAPI BOOL WINAPI
SetupDiEnumDeviceInterfaces(
IN HDEVINFO DeviceInfoSet, // 设备信息集合
IN PSP_DEVINFO_DATA DeviceInfoData, OPTIONAL// 设备成员
IN LPGUID InterfaceClassGuid, // 接口GUID
IN DWORD MemberIndex, // 接口在集合中的Index
OUT PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData// 返回的接口信息
);
DeviceInfoSet
是“设备信息集合”变量,即SetupDiGetClassDevs
调用的返回值。
DeviceInfoData
值很少有人会设置,但如果设置了此值,则函数枚举过程中,将只枚举集合中DeviceInfoData
包含的设备接口类。打比方说,DeviceInfoData
所代表的设备支持GUID1和GUID2两个设备接口类,则函数在集合中将仅寻找这两类设备接口,而不管其他。
由于此函数用来获取设备接口,而一个集合设置同一个设备中,可包含多种设备接口类,所以参数InterfaceClassGuid
用来设置设备接口GUID。
MemberIndex
意义与前相同,调用时应将其初始值设置为0,循环调用,直到MemberIndex值超出集合范围使得SetupDiEnumDeviceInterfaces
返回FALSE,可结束递增循环。
DeviceInterfaceData
中返回设备接口信息。
typedef struct _SP_DEVICE_INTERFACE_DATA {
DWORD cbSize;
GUID InterfaceClassGuid;
DWORD Flags;
ULONG_PTR Reserved;
} SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA;
BOOL SetupDiGetDeviceInterfaceDetail(
_In_ HDEVINFO DeviceInfoSet,
_In_ PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData,
_Out_opt_ PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
_In_ DWORD DeviceInterfaceDetailDataSize,
_Out_opt_ PDWORD RequiredSize,
_Out_opt_ PSP_DEVINFO_DATA DeviceInfoData
);
typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA {
DWORD cbSize;
TCHAR DevicePath[ANYSIZE_ARRAY];
} SP_DEVICE_INTERFACE_DETAIL_DATA, *PSP_DEVICE_INTERFACE_DETAIL_DATA;
SetupDiGetDeviceRegistryProperty
函数用来检索指定的即插即用设备特性(这个函数主要是使用System\CurrentControlSet\Enum注册表来查询相关硬件信息)
BOOL SetupDiGetDeviceRegistryProperty(
_In_ HDEVINFO DeviceInfoSet,
_In_ PSP_DEVINFO_DATA DeviceInfoData,
_In_ DWORD Property,
_Out_opt_ PDWORD PropertyRegDataType,
_Out_opt_ PBYTE PropertyBuffer,
_In_ DWORD PropertyBufferSize,
_Out_opt_ PDWORD RequiredSize
);
DeviceInfoSet
: 设备信息句柄。
DeviceInfoData
: SP_DEVINFO_DATA
结构体,包含DeviceInfoSet
中的设备信息
Property取以下的值:
SPDRP_ADDRESS : 查询设备的地址
SPDRP_BUSNUMBER : 查询设备的bus号
SPDRP_BUSTYPEGUID : 查询设备的GUID号
… …
这个函数的主要原理是:从注册表中读取PnP设备的属性。
SetupDiDestroyDeviceInfoList
销毁一个设备信息集合,并且释放所有关联的内存
BOOL SetupDiDestroyDeviceInfoList( HDEVINFO DeviceInfoSet );
DeviceInfoSet
: 要释放的设备信息句柄
成功返回非零,否则返回零
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{4d36e97d-e325-11ce-bfc1-08002be10318}
获取设备安装类的枚举信息。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum
下面设备相关信息。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class
:是设备安装类GUID,代表当前设备类型。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
:设备接口类GUIDHKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses
获取设备接口的枚举信息。任何一种驱动都要为用户,或者其他使用者提供某种类型的名字。而使用者也通过这种名字来区别不同的设备,并与之进行IO。
在Windows NT 4.0和以前的版本中,驱动程序会为设备对象创建名字,并为之创建设备连接符,并将之注册到系统中。
自Windows 2000开始,驱动程序不再为设备对象命名,而是使用设备接口类。设备接口类可以向使用者提供设备和驱动程序函数的入口,驱动将注册设备接口类,并为设备对象创建设备接口类实例。
每个设备接口类都有一个GUID。系统在device-specific header files中定义了常用设备类与其GUID。但设备开发者可以自定义设备类。
例如,三种不同类型的鼠标可以属于同一个设备接口类,即使他们分别使用了USB口,串口,红外端口。他们的驱动都把他注册为GUID_DEVINTERFACE_MOUSE
设备接口类,这个设备接口类的GUID在Ntddmou.h中定义。
特别指出,这些驱动只注册了一个设备类。然而,各种设备的驱动可以有专门的函数以注册除标准接口类以外的接口类,如可安装磁盘驱动必须注册(GUID_DEVINTERFACE_DISK
) 接口类以及(MOUNTDEV_MOUNTED_DEVICE_GUID
)可安装设备类。
当驱动注册了设备接口的实例后,IO管理器就将设备和设备接口的GUID,符号链接名联系在一起。符号链接名存储在注册表中,在系统启动时即存在。使用某用户可以查询这个接口以获得支持这个接口的设备的符号链接名。使用者可以用这个符号连接名来对设备进行IO.
为了便于设备的安装,具有同种安装方式的设备被归为同一个设备安装类,例如 SCSI 媒体转换设备被归类到MediumChanger device setup class中,设备安装类中定义了在设备安装过程中设备安装程序与其相关程序所使用到的类。
微软为大部分设备定义了标准设备安装类,设备开发者可以自定义设备安装类,但必须是在标准设备安装类都不能使用的情况下。
每个设备安装类都有一个GUID,系统在Devguid.h给出了各设备安装类的GUID,并且给定了相应的符号连接名。
设备安装类为属于自己的设备定义了..\CurrentControlSet\Control\Class\ClassGuid
下的一个子键。
开发者可以通过INF文件来创建一个新的设备安装类。
NTSTATUS
IoRegisterDeviceInterface(
IN PDEVICE_OBJECT PhysicalDeviceObject,
IN CONST GUID *InterfaceClassGuid,
IN PUNICODE_STRING ReferenceString OPTIONAL,
OUT PUNICODE_STRING SymbolicLinkName
);
InterfaceClassGuid
这个指定了接口类的GUID,如果指定了一个新有的GUID,那么,将在设备接口类注册表中新生成一个新的表项,如下:
DEFINE_GUID(IOM_RING_INTERFACE,
0xfdcac3d6, 0xe85f, 0x4f22, 0xa2, 0x3c, 0x75, 0xe2, 0xbd, 0xef, 0x7f, 0x62);
nStatus = IoRegisterDeviceInterface(PhysicalDeviceObject, &IOM_RING_INTERFACE, NULL, &pDeviceExtension->InterfaceName);
那么生成的注册表项为HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{fdcac3d6-e85f-4f22-a23c-75e2bdef7f62}
。
下面提供一个代码,查询能够打开工作的USB的设备接口:
DEFINE_GUID (UsbClassGuid, 0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed);
int _tmain(int argc, _TCHAR* argv[])
{
HDEVINFO hDevInfo;
SP_DEVICE_INTERFACE_DATA spDevData;
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
BOOL bRes = TRUE;
int nCount = 0;
hDevInfo = ::SetupDiGetClassDevs(NULL,NULL,NULL,DIGCF_ALLCLASSES | DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
if (hDevInfo != INVALID_HANDLE_VALUE)
{
pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT,1024);
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
while (bRes)
{
spDevData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
bRes = ::SetupDiEnumDeviceInterfaces(hDevInfo,NULL,(LPGUID)&UsbClassGuid,nCount,&spDevData);
if (bRes)
{
bRes = ::SetupDiGetInterfaceDeviceDetail(hDevInfo,&spDevData,pDetail,1024,NULL,NULL);
if (bRes)
{
wcout << L"success : " << pDetail->DevicePath << endl;
nCount ++;
}
}
}
::GlobalFree(pDetail);
::SetupDiDestroyDeviceInfoList(hDevInfo);
}
return 0;
}
这个代码得到的返回结果如下:
success : \\?\usb#vid_24ae&pid_1813#5&262ed807&1&3#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
success : \\?\usb#vid_0bda&pid_58dd#200901010001#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
枚举PCIe或者USB设备可以通过如下GUID:
SetupDiGetClassDevs( (LPGUID)&GUID_DEVINTERFACE_DISK,
NULL,
NULL,
(DIGCF_PRESENT | // Only Devices present
DIGCF_INTERFACEDEVICE));
SetupDiEnumDeviceInterfaces(
hardwareDeviceInfo,
0,
(LPGUID)&GUID_DEVINTERFACE_DISK,
i,
&deviceInterfaceData);
SetupDiGetInterfaceDeviceDetail(
hardwareDeviceInfo,
&deviceInterfaceData,
NULL,
0,
&requiredLength,
NULL);
SearchStr(deviceInterfaceDetailData->DevicePath, (char *)"nvme");
SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
SetupDiGetClassDevs( (LPGUID)&GUID_USBSTOR_INTERFACE_CLASS,
NULL,
NULL,
(DIGCF_PRESENT | // Only Devices present
DIGCF_INTERFACEDEVICE));
SetupDiEnumDeviceInterfaces(
hardwareDeviceInfo,
0,
(LPGUID)&GUID_USBSTOR_INTERFACE_CLASS,
i, //
&deviceInterfaceData)
SetupDiGetInterfaceDeviceDetail(
hardwareDeviceInfo,
&deviceInterfaceData,
NULL,
0,
&requiredLength,
NULL);
SearchStr(deviceInterfaceDetailData->DevicePath, (char *)"usbstor");
SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);