You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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使用步骤:

  1. 打开Visual Studio,新建一个控制台应用项目(C++)
  2. 把上面的代码替换掉默认的main.cpp内容
  3. 直接编译运行即可,不需要额外配置任何库(因为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:32