我之前有发过一篇文章《Qt篇------获取Windows系统上插入的串口设备的物理序号》,文章中主要获取的是插入的USB串口设备的物理序号;而本篇文章则进行拓展,可以获取所有外接设备的相关信息(比如USB摄像头、USB蓝牙、USB网卡、其它一些可拔插的设备等等),并且代码进行了优化、精简。代码如下,可直接cv享用。
cpp
#include <QDebug>
#include <windows.h>
#include <setupapi.h>
#include <devguid.h>
#pragma comment(lib, "setupapi.lib")
void DevicesTool::listCameras() {
// 获取所有图像设备, GUID_DEVCLASS_USB、GUID_DEVCLASS_SYSTEM等等都可以,根据实际需求选择
HDEVINFO deviceInfoSet = SetupDiGetClassDevs(&GUID_DEVCLASS_USB, nullptr, nullptr, DIGCF_PRESENT | DIGCF_ALLCLASSES);
if (deviceInfoSet == INVALID_HANDLE_VALUE) {
qDebug() << "Failed to get device info set";
return;
}
SP_DEVINFO_DATA deviceInfoData;
deviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
// 遍历设备列表
for (DWORD i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, &deviceInfoData); i++) {
TCHAR deviceName[256];
QString deviceDescStr = "", deviceNameStr = "", deviceLocationInfoStr = "", deviceLocationPathStr;
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_DEVICEDESC, nullptr,
(PBYTE)deviceName, sizeof(deviceName), nullptr)) {
deviceDescStr= QString::fromWCharArray(deviceName);
}
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_FRIENDLYNAME, nullptr,
(PBYTE)deviceName, sizeof(deviceName), nullptr)) {
deviceNameStr = QString::fromWCharArray(deviceName);
}
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_LOCATION_INFORMATION, nullptr,
(PBYTE)deviceName, sizeof(deviceName), nullptr)) {
deviceLocationInfoStr = QString::fromWCharArray(deviceName);
}
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_LOCATION_PATHS, nullptr,
(PBYTE)deviceName, sizeof(deviceName), nullptr)) {
deviceLocationPathStr = QString::fromWCharArray(deviceName);
}
if (deviceLocationInfoStr != "" && deviceDescStr != "" && deviceNameStr != "" && deviceLocationPathStr != "") {
qDebug() << "Device: " << deviceDescStr << deviceNameStr << deviceLocationInfoStr << deviceLocationPathStr;
}
//可以再筛选一条属性
/**
int index = -1;
if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, &deviceInfoData, SPDRP_DRIVER, nullptr,
(PBYTE)deviceName, sizeof(deviceName), nullptr)) {
index = QString::fromWCharArray(deviceName).split("\\")[1].toInt();
}
if (deviceLocationInfoStr != "" && deviceDescStr != ""
&& deviceNameStr != "" && deviceLocationPathStr != "" && index != -1) {
qDebug() << "Device: " << deviceDescStr << deviceNameStr << deviceLocationInfoStr << deviceLocationPathStr << index;
cameraList.append(new DeviceInfo(deviceNameStr, deviceDescStr, deviceLocationInfoStr, deviceLocationPathStr, index));
}
*/
}
SetupDiDestroyDeviceInfoList(deviceInfoSet);
}
打印结果如下:
比如我获取到的4个USB 视频设备就是我插入的3个USB相机、系统自带的相机,这里就可以知道哪个相机插在哪个USB口上面了(右键此电脑-管理-设备管理器-照相机,双击我要查看的相机,里面的"位置"就是上面代码中获取到的deviceLocationInfoStr)。
ps:其实还有更多设备信息可以查看,只是我打印的地方进行了筛选,只有4项信息(物理端口信息、物理端口路径、设备信息、设备名称)都能够获取的才进行打印,可以根据你实际需求降低筛选。
需要了解代码原理的看这里,这里简单地介绍各个函数的用途:
1.SetupDiGetClassDevs函数返回一个包含本机上所有被请求的设备信息的设备信息集合句柄。
HDEVINFO SetupDiGetClassDevs(
_In_opt_ const GUID *ClassGuid, // 一个指向GUID的指针,此GUID可标识一个设备安装类或一个设备接口类, 可以为NULL
_In_opt_ PCTSTR Enumerator, // 一个指向以空字符结束的字符串的指针
_In_opt_ HWND hwndParent, // 用于与在设备信息集中安装设备实例相关联的用户界面的顶级https://zhida.zhihu.com/search?q=%E7%AA%97%E5%8F%A3%E5%8F%A5%E6%9F%84&zhida_source=entity&is_preview=1
_In_ DWORD Flags // 通过此参数来过滤指定的设备信息集中的设备, DIGCF_PRESENT表示只返回当前系统中存在的(已连接)设备。
);
该函数的第一个入口参数GUID指定了我们想要检索什么类型的设备,一些常用的GUID如下:
GUID_DEVCLASS_SYSTEM // 系统设备GUID
GUID_DEVCLASS_USB // USB设备GUID
GUID_DEVCLASS_MOUSE // 鼠标设备GUID
GUID_DEVCLASS_NET // 网络设备GUID
GUID_DEVCLASS_KEYBOARD // 键盘设备GUID
当调用完此函数并处理完相应数据后,必须调用SetupDiDestroyDeviceInfoList函数,否则内存溢出。
2.SetupDiEnumDeviceInfo函数返回一个SP_DEVINFO_DATA结构,它指定该设备的信息集的设备的信息元素。
BOOL SetupDiEnumDeviceInfo(
_In_ HDEVINFO DeviceInfoSet, // 设备信息集的句柄,即SetupDiGetClassDevs返回的句柄
_In_ DWORD MemberIndex, // 要检索的设备信息元素的从零开始的索引
_Out_ PSP_DEVINFO_DATA DeviceInfoData // 指向SP_DEVINFO_DATA结构的指针,以接收有关枚举设备信息元素的信息
);
3.SetupDiGetDeviceRegistryProperty检索指定的即插即用设备属性.
BOOL SetupDiGetDeviceRegistryPropertyW(
_In_ HDEVINFO DeviceInfoSet, // 设备信息集的句柄,即SetupDiGetClassDevs返回的句柄
_In_ PSP_DEVINFO_DATA DeviceInfoData, // 指向SP_DEVINFO_DATA结构的指针,该结构指定DeviceInfoSet中的设备信息元素
_In_ DWORD Property, // 指定要检索的属性
_Out_opt_ PDWORD PropertyRegDataType, // 指向一个变量的指针,该变量接收要检索的属性的数据类型。
_Out_writes_bytes_to_opt_(PropertyBufferSize, *RequiredSize) PBYTE PropertyBuffer, // 指向缓冲区的指针,该缓冲区接收正在检索的属性
_In_ DWORD PropertyBufferSize, // PropertyBuffer缓冲区的大小(单位:字节)
_Out_opt_ PDWORD RequiredSize // 指向DWORD类型的变量的指针,该变量接收所需的PropertyBuffer缓冲区的大小(单位:字节)
);
该函数的第三个入口参数Property决定了我们想要检索设备的什么属性,它的取值可以在SetupAPI.h文件里查找,一些常用的属性的含义如下:
#常用
#define SPDRP_DEVICEDESC (0x00000000) // DeviceDesc (R/W)
#define SPDRP_FRIENDLYNAME (0x0000000C) // FriendlyName (R/W)
#define SPDRP_LOCATION_INFORMATION (0x0000000D) // LocationInformation (R/W)
#其他
#define SPDRP_HARDWAREID (0x00000001) // HardwareID (R/W)
#define SPDRP_COMPATIBLEIDS (0x00000002) // CompatibleIDs (R/W)
#define SPDRP_CLASS (0x00000007) // Class (R--tied to ClassGUID)
#define SPDRP_CLASSGUID (0x00000008) // ClassGUID (R/W)