基于C++/Visual Studio实现USB设备枚举及插拔监听求助
问题分析
你当前的代码用GetVolumeInformation枚举USB设备,这个方法的局限性很明显:它只能识别被系统分配了盘符的存储类USB设备(比如U盘、移动硬盘)。而手机通常是以MTP(媒体传输协议)或PTP(图片传输协议)模式连接的,这类设备不会被分配盘符,所以你的代码根本检测不到它们。
另外,你的代码也没有监听设备插拔事件的能力,只能一次性扫描。下面我给你两个基于Windows原生API的方案,不需要额外引入第三方库,VS直接就能用,代码里会加详细注释,适合刚接触C++的新手。
方案一:枚举所有已连接的USB设备(含非存储类,比如手机)
这个方案用Windows的SetupAPI,它是系统自带的设备管理API,专门用来枚举各种硬件设备,包括USB设备。下面是完整代码:
#include <iostream> #include <windows.h> #include <setupapi.h> #include <initguid.h> #include <devguid.h> // 链接SetupAPI库,VS里会自动处理,不用额外配置 #pragma comment(lib, "setupapi.lib") void EnumerateUSB() { HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (hDevInfo == INVALID_HANDLE_VALUE) { std::cout << "获取设备列表失败,错误码:" << GetLastError() << std::endl; return; } SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, i, &deviceInterfaceData); i++) { // 获取设备详情所需的缓冲区大小 DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); // 分配缓冲区 PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize)); if (!pDetailData) continue; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); SP_DEVINFO_DATA devInfoData = {0}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { // 获取设备描述名 char deviceName[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, reinterpret_cast<PBYTE>(deviceName), MAX_PATH, NULL)) { std::cout << "USB设备名称:" << deviceName << std::endl; } // 获取设备实例ID(可以用来区分不同设备) char deviceInstanceId[MAX_PATH] = {0}; if (CM_Get_Device_ID(devInfoData.DevInst, deviceInstanceId, MAX_PATH, 0) == CR_SUCCESS) { std::cout << "设备实例ID:" << deviceInstanceId << std::endl; } std::cout << "-------------------------" << std::endl; } free(pDetailData); } SetupDiDestroyDeviceInfoList(hDevInfo); } int main() { std::cout << "当前已连接的USB设备列表:" << std::endl; std::cout << "-------------------------" << std::endl; EnumerateUSB(); return 0; }
代码说明:
SetupDiGetClassDevs:获取所有当前存在的USB设备接口列表SetupDiEnumDeviceInterfaces:逐个枚举设备SetupDiGetDeviceInterfaceDetail:获取设备的详细信息(比如设备路径)SetupDiGetDeviceRegistryProperty:从注册表读取设备的友好名称(就是你在设备管理器里看到的名字)CM_Get_Device_ID:获取设备的唯一实例ID,用来区分不同设备
VS使用步骤:
- 打开Visual Studio,新建一个控制台应用项目(C++)
- 把上面的代码替换掉默认的
main.cpp内容 - 直接编译运行即可,不需要额外配置任何库(因为SetupAPI是Windows系统自带的,VS默认链接相关库)
方案二:监听USB设备的连接/断开事件
要监听设备插拔,我们需要利用Windows的设备通知机制,这个机制需要一个窗口来接收消息(即使是控制台程序,也可以创建一个隐藏窗口来处理)。下面是完整代码:
#include <iostream> #include <windows.h> #include <setupapi.h> #include <initguid.h> #include <devguid.h> #pragma comment(lib, "setupapi.lib") #pragma comment(lib, "user32.lib") // 全局窗口句柄 HWND g_hWnd = NULL; // 窗口过程函数,处理设备通知消息 LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DEVICECHANGE: { if (wParam == DBT_DEVICEARRIVAL) { // 设备连接事件 PDEV_BROADCAST_HDR pHdr = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam); if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { PDEV_BROADCAST_DEVICEINTERFACE pDevInf = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lParam); if (IsEqualGUID(pDevInf->dbcc_classguid, GUID_DEVINTERFACE_USB_DEVICE)) { std::cout << "检测到USB设备连接!" << std::endl; // 这里可以调用方案一里的EnumerateUSB()来更新设备列表 std::cout << "更新后的设备列表:" << std::endl; std::cout << "-------------------------" << std::endl; // 重新枚举设备 HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (hDevInfo != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, i, &deviceInterfaceData); i++) { DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize)); if (!pDetailData) continue; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); SP_DEVINFO_DATA devInfoData = {0}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { char deviceName[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, reinterpret_cast<PBYTE>(deviceName), MAX_PATH, NULL)) { std::cout << "USB设备名称:" << deviceName << std::endl; } } free(pDetailData); } SetupDiDestroyDeviceInfoList(hDevInfo); } std::cout << "-------------------------" << std::endl; } } } else if (wParam == DBT_DEVICEREMOVECOMPLETE) { // 设备断开事件 PDEV_BROADCAST_HDR pHdr = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam); if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) { PDEV_BROADCAST_DEVICEINTERFACE pDevInf = reinterpret_cast<PDEV_BROADCAST_DEVICEINTERFACE>(lParam); if (IsEqualGUID(pDevInf->dbcc_classguid, GUID_DEVINTERFACE_USB_DEVICE)) { std::cout << "检测到USB设备断开!" << std::endl; // 同样可以调用EnumerateUSB()更新列表 std::cout << "更新后的设备列表:" << std::endl; std::cout << "-------------------------" << std::endl; HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (hDevInfo != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, i, &deviceInterfaceData); i++) { DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize)); if (!pDetailData) continue; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); SP_DEVINFO_DATA devInfoData = {0}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { char deviceName[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, reinterpret_cast<PBYTE>(deviceName), MAX_PATH, NULL)) { std::cout << "USB设备名称:" << deviceName << std::endl; } } free(pDetailData); } SetupDiDestroyDeviceInfoList(hDevInfo); } std::cout << "-------------------------" << std::endl; } } } break; } case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; } // 注册窗口类并创建隐藏窗口 bool CreateHiddenWindow() { const char* className = "USBDeviceMonitorWindow"; WNDCLASSEXA wc = {0}; wc.cbSize = sizeof(WNDCLASSEXA); wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandle(NULL); wc.lpszClassName = className; if (!RegisterClassExA(&wc)) { std::cout << "注册窗口类失败,错误码:" << GetLastError() << std::endl; return false; } g_hWnd = CreateWindowExA(0, className, "USB Monitor", 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL); if (!g_hWnd) { std::cout << "创建窗口失败,错误码:" << GetLastError() << std::endl; return false; } return true; } // 注册USB设备通知 bool RegisterUSBDeviceNotification() { DEV_BROADCAST_DEVICEINTERFACE_NOTIFY_FILTER filter = {0}; filter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_NOTIFY_FILTER); filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; filter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE; HDEVNOTIFY hDevNotify = RegisterDeviceNotification(g_hWnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE); if (!hDevNotify) { std::cout << "注册设备通知失败,错误码:" << GetLastError() << std::endl; return false; } return true; } int main() { std::cout << "USB设备监听已启动,按Ctrl+C退出..." << std::endl; std::cout << "初始设备列表:" << std::endl; std::cout << "-------------------------" << std::endl; // 先枚举一次初始设备 HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); if (hDevInfo != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA deviceInterfaceData = {0}; deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, i, &deviceInterfaceData); i++) { DWORD requiredSize = 0; SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL); PSP_DEVICE_INTERFACE_DETAIL_DATA pDetailData = reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize)); if (!pDetailData) continue; pDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); SP_DEVINFO_DATA devInfoData = {0}; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, pDetailData, requiredSize, NULL, &devInfoData)) { char deviceName[MAX_PATH] = {0}; if (SetupDiGetDeviceRegistryProperty(hDevInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, reinterpret_cast<PBYTE>(deviceName), MAX_PATH, NULL)) { std::cout << "USB设备名称:" << deviceName << std::endl; } } free(pDetailData); } SetupDiDestroyDeviceInfoList(hDevInfo); } std::cout << "-------------------------" << std::endl; // 创建隐藏窗口并注册设备通知 if (!CreateHiddenWindow() || !RegisterUSBDeviceNotification()) { return 1; } // 消息循环,处理设备通知 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return 0; }
代码说明:
CreateHiddenWindow:创建一个隐藏的消息窗口,用来接收Windows的设备通知消息RegisterUSBDeviceNotification:告诉系统我们要监听USB设备的插拔事件WindowProc:窗口消息处理函数,当有设备连接/断开时,会收到WM_DEVICECHANGE消息,我们在这个函数里处理事件并更新设备列表- 消息循环:
GetMessage、TranslateMessage、DispatchMessage是Windows程序的核心,用来处理系统消息
VS使用步骤:
和方案一一样,新建控制台项目,替换代码,直接编译运行即可。运行后程序会一直监听,你可以插拔手机或其他USB设备,程序会实时输出设备列表的变化。
为什么不用libusb?
libusb确实可以实现更底层的USB操作,但对于你的需求来说,SetupAPI已经足够,而且不需要额外配置第三方库,对于刚接触C++的新手更友好。如果你之后需要更底层的操作(比如读写USB设备的端点),再考虑学习libusb也不迟。
内容的提问来源于stack exchange,提问作者Liton Rahman
相关产品推荐
相关产品推荐

