代码改变世界

Windows实现USB设备热插拔功能

2025-05-30 18:38  一只老老老菜鸟  阅读(281)  评论(0)    收藏  举报

https://learn.microsoft.com/zh-cn/windows/win32/devio/detecting-media-insertion-or-removal

### 实现步骤

 

1. **注册设备通知**:

- 使用`RegisterDeviceNotification`注册要监听的设备类型

 

2. **添加设备变更消息处理**:

- 在窗口类中处理`WM_DEVICECHANGE`消息

- 判断设备类型(如USB设备)和事件类型(设备插入、移除)

- 通过pidvid找到对应设备

- 调用回调或发送自定义消息到ui,更新ui

3.如需实现其他类型设备监听,更改设备接口类GUID和判断设备类型

完整实现代码

DeviceMonitor.h 头文件

#pragma once
#include <Dbt.h>
#include <SetupAPI.h>
#include <cfgmgr32.h>
#include <string>
#include <vector>

#pragma comment(lib, "setupapi.lib")

// 设备信息结构
struct DeviceInfo {
    std::wstring devicePath;      // 设备路径
    std::wstring description;     // 设备描述
    std::wstring manufacturer;    // 制造商
    std::wstring instanceId;      // 设备实例ID
};

class CDeviceMonitor {
public:
    CDeviceMonitor();
    virtual ~CDeviceMonitor();

    // 初始化设备监控
    BOOL Initialize(CWnd* pNotifyWnd);
    
    // 获取当前连接的设备列表
    std::vector<DeviceInfo> GetConnectedDevices() const;
    
    // 处理设备变更消息
    LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam);

private:
    // 注册设备通知
    BOOL RegisterDeviceNotification();
    
    // 注销设备通知
    void UnregisterDeviceNotification();
    
    // 获取设备详细信息
    DeviceInfo GetDeviceInfo(const wchar_t* devicePath) const;
    
    // 枚举所有连接的设备
    void EnumerateConnectedDevices();

private:
    CWnd* m_pNotifyWnd;           // 接收通知的窗口
    HDEVNOTIFY m_hDevNotify;      // 设备通知句柄
    GUID m_deviceInterfaceGUID;   // 要监控的设备类GUID
    std::vector<DeviceInfo> m_connectedDevices; // 已连接设备列表
    mutable CCriticalSection m_csDeviceList;    // 设备列表锁
};

DeviceMonitor.cpp 实现文件

#include "stdafx.h"
#include "DeviceMonitor.h"
#include <initguid.h> // 必须包含以使用GUID定义

// 定义USB设备接口GUID
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 
    0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);

CDeviceMonitor::CDeviceMonitor() 
    : m_pNotifyWnd(nullptr)
    , m_hDevNotify(nullptr) {
    // 设置监控USB设备
    m_deviceInterfaceGUID = GUID_DEVINTERFACE_USB_DEVICE;
}

CDeviceMonitor::~CDeviceMonitor() {
    UnregisterDeviceNotification();
}

// 初始化设备监控
BOOL CDeviceMonitor::Initialize(CWnd* pNotifyWnd) {
    if (!pNotifyWnd || !pNotifyWnd->GetSafeHwnd()) {
        return FALSE;
    }
    
    m_pNotifyWnd = pNotifyWnd;
    
    // 枚举当前已连接的设备
    EnumerateConnectedDevices();
    
    // 注册设备通知
    return RegisterDeviceNotification();
}

// 注册设备通知
BOOL CDeviceMonitor::RegisterDeviceNotification() {
    if (!m_pNotifyWnd) return FALSE;
    
    DEV_BROADCAST_DEVICEINTERFACE notificationFilter;
    ZeroMemory(&notificationFilter, sizeof(notificationFilter));
    
    notificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    notificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    notificationFilter.dbcc_classguid = m_deviceInterfaceGUID;
    
    // 注册设备通知
    m_hDevNotify = ::RegisterDeviceNotification(
        m_pNotifyWnd->GetSafeHwnd(),
        &notificationFilter,
        DEVICE_NOTIFY_WINDOW_HANDLE
    );
    
    return (m_hDevNotify != nullptr);
}

// 注销设备通知
void CDeviceMonitor::UnregisterDeviceNotification() {
    if (m_hDevNotify) {
        ::UnregisterDeviceNotification(m_hDevNotify);
        m_hDevNotify = nullptr;
    }
}

// 处理设备变更消息
LRESULT CDeviceMonitor::OnDeviceChange(WPARAM wParam, LPARAM lParam) {
    if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam;
        
        if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
            PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
            
            // 获取设备路径
            std::wstring devicePath = pDevInf->dbcc_name;
            
            if (wParam == DBT_DEVICEARRIVAL) {
                // 设备插入
                DeviceInfo info = GetDeviceInfo(devicePath.c_str());
                
                CSingleLock lock(&m_csDeviceList, TRUE);
                m_connectedDevices.push_back(info);
                
                // 发送自定义消息通知UI更新
                if (m_pNotifyWnd) {
                    m_pNotifyWnd->PostMessage(WM_DEVICE_ARRIVED, 
                        (WPARAM)new DeviceInfo(info));
                }
            }
            else if (wParam == DBT_DEVICEREMOVECOMPLETE) {
                // 设备移除
                CSingleLock lock(&m_csDeviceList, TRUE);
                
                // 从列表中移除设备
                auto it = std::remove_if(m_connectedDevices.begin(), m_connectedDevices.end(),
                    [&devicePath](const DeviceInfo& info) {
                        return _wcsicmp(info.devicePath.c_str(), devicePath.c_str()) == 0;
                    });
                
                if (it != m_connectedDevices.end()) {
                    DeviceInfo removedDevice = *it;
                    m_connectedDevices.erase(it, m_connectedDevices.end());
                    
                    // 发送自定义消息通知UI更新
                    if (m_pNotifyWnd) {
                        m_pNotifyWnd->PostMessage(WM_DEVICE_REMOVED, 
                            (WPARAM)new DeviceInfo(removedDevice));
                    }
                }
            }
        }
    }
    
    return TRUE;
}

// 获取设备详细信息
DeviceInfo CDeviceMonitor::GetDeviceInfo(const wchar_t* devicePath) const {
    DeviceInfo info;
    info.devicePath = devicePath;
    
    // 获取设备信息集句柄
    HDEVINFO hDevInfo = SetupDiGetClassDevs(
        &m_deviceInterfaceGUID,
        NULL,
        NULL,
        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
    );
    
    if (hDevInfo == INVALID_HANDLE_VALUE) {
        return info;
    }
    
    SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
    devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    
    DWORD memberIndex = 0;
    while (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &m_deviceInterfaceGUID, memberIndex, &devInterfaceData)) {
        DWORD requiredSize = 0;
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, NULL, 0, &requiredSize, NULL);
        
        PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize);
        if (pDetail == NULL) {
            memberIndex++;
            continue;
        }
        
        pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        SP_DEVINFO_DATA devInfoData = {0};
        devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
        
        if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, pDetail, requiredSize, NULL, &devInfoData)) {
            if (_wcsicmp(pDetail->DevicePath, devicePath) == 0) {
                // 获取设备描述
                WCHAR desc[256] = {0};
                if (SetupDiGetDeviceRegistryProperty(
                    hDevInfo, &devInfoData, SPDRP_DEVICEDESC, NULL, 
                    (PBYTE)desc, sizeof(desc), NULL)) 
                {
                    info.description = desc;
                }
                
                // 获取制造商
                WCHAR mfg[256] = {0};
                if (SetupDiGetDeviceRegistryProperty(
                    hDevInfo, &devInfoData, SPDRP_MFG, NULL, 
                    (PBYTE)mfg, sizeof(mfg), NULL)) 
                {
                    info.manufacturer = mfg;
                }
                
                // 获取设备实例ID
                WCHAR instanceId[256] = {0};
                if (CM_Get_Device_ID(devInfoData.DevInst, instanceId, sizeof(instanceId)/sizeof(WCHAR), 0) == CR_SUCCESS) {
                    info.instanceId = instanceId;
                }
                
                free(pDetail);
                break;
            }
        }
        
        free(pDetail);
        memberIndex++;
    }
    
    SetupDiDestroyDeviceInfoList(hDevInfo);
    return info;
}

// 枚举所有连接的设备
void CDeviceMonitor::EnumerateConnectedDevices() {
    HDEVINFO hDevInfo = SetupDiGetClassDevs(
        &m_deviceInterfaceGUID,
        NULL,
        NULL,
        DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
    );
    
    if (hDevInfo == INVALID_HANDLE_VALUE) {
        return;
    }
    
    SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
    devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    
    DWORD memberIndex = 0;
    while (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &m_deviceInterfaceGUID, memberIndex, &devInterfaceData)) {
        DWORD requiredSize = 0;
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, NULL, 0, &requiredSize, NULL);
        
        PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(requiredSize);
        if (pDetail == NULL) {
            memberIndex++;
            continue;
        }
        
        pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
        SP_DEVINFO_DATA devInfoData = {0};
        devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
        
        if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInterfaceData, pDetail, requiredSize, NULL, &devInfoData)) {
            DeviceInfo info = GetDeviceInfo(pDetail->DevicePath);
            m_connectedDevices.push_back(info);
        }
        
        free(pDetail);
        memberIndex++;
    }
    
    SetupDiDestroyDeviceInfoList(hDevInfo);
}

// 获取当前连接的设备列表
std::vector<DeviceInfo> CDeviceMonitor::GetConnectedDevices() const {
    CSingleLock lock(&m_csDeviceList, TRUE);
    return m_connectedDevices;
}

MyDialog.h 对话框头文件

#pragma once
#include "DeviceMonitor.h"

// 自定义消息
#define WM_DEVICE_ARRIVED (WM_USER + 200)
#define WM_DEVICE_REMOVED (WM_USER + 201)

class CMyDialog : public CDialogEx {
    DECLARE_DYNAMIC(CMyDialog)
public:
    CMyDialog(CWnd* pParent = nullptr);
    virtual ~CMyDialog();

protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual BOOL OnInitDialog();
    
    // 设备监控器
    CDeviceMonitor m_deviceMonitor;
    
    // 消息处理
    afx_msg void OnPaint();
    afx_msg LRESULT OnDeviceChange(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnDeviceArrived(WPARAM wParam, LPARAM lParam);
    afx_msg LRESULT OnDeviceRemoved(WPARAM wParam, LPARAM lParam);
    
    // 更新UI
    void UpdateDeviceList();
    
    DECLARE_MESSAGE_MAP()
    
private:
    CListBox m_deviceListBox; // 设备列表控件
};

MyDialog.cpp 对话框实现文件

#include "stdafx.h"
#include "MyDialog.h"
#include "resource.h"

IMPLEMENT_DYNAMIC(CMyDialog, CDialogEx)

CMyDialog::CMyDialog(CWnd* pParent) 
    : CDialogEx(IDD_MYDIALOG, pParent) {
}

CMyDialog::~CMyDialog() {
}

void CMyDialog::DoDataExchange(CDataExchange* pDX) {
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_DEVICE_LIST, m_deviceListBox);
}

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_WM_PAINT()
    ON_MESSAGE(WM_DEVICECHANGE, OnDeviceChange)
    ON_MESSAGE(WM_DEVICE_ARRIVED, OnDeviceArrived)
    ON_MESSAGE(WM_DEVICE_REMOVED, OnDeviceRemoved)
END_MESSAGE_MAP()

BOOL CMyDialog::OnInitDialog() {
    CDialogEx::OnInitDialog();
    
    // 设置对话框标题
    SetWindowText(_T("设备热插拔监控"));
    
    // 初始化设备监控
    if (!m_deviceMonitor.Initialize(this)) {
        AfxMessageBox(_T("无法初始化设备监控"));
    }
    
    // 更新设备列表
    UpdateDeviceList();
    
    return TRUE;
}

void CMyDialog::OnPaint() {
    CPaintDC dc(this);
    // 绘制代码...
}

// 处理设备变更消息
LRESULT CMyDialog::OnDeviceChange(WPARAM wParam, LPARAM lParam) {
    return m_deviceMonitor.OnDeviceChange(wParam, lParam);
}

// 处理设备插入消息
LRESULT CMyDialog::OnDeviceArrived(WPARAM wParam, LPARAM lParam) {
    DeviceInfo* pInfo = reinterpret_cast<DeviceInfo*>(wParam);
    if (pInfo) {
        CString msg;
        msg.Format(_T("设备插入: %s"), pInfo->description.c_str());
        AfxMessageBox(msg);
        
        // 更新设备列表
        UpdateDeviceList();
        
        delete pInfo;
    }
    return 0;
}

// 处理设备移除消息
LRESULT CMyDialog::OnDeviceRemoved(WPARAM wParam, LPARAM lParam) {
    DeviceInfo* pInfo = reinterpret_cast<DeviceInfo*>(wParam);
    if (pInfo) {
        CString msg;
        msg.Format(_T("设备移除: %s"), pInfo->description.c_str());
        AfxMessageBox(msg);
        
        // 更新设备列表
        UpdateDeviceList();
        
        delete pInfo;
    }
    return 0;
}

// 更新设备列表
void CMyDialog::UpdateDeviceList() {
    m_deviceListBox.ResetContent();
    
    std::vector<DeviceInfo> devices = m_deviceMonitor.GetConnectedDevices();
    for (const auto& device : devices) {
        CString entry;
        entry.Format(_T("%s [%s]"), device.description.c_str(), device.manufacturer.c_str());
        m_deviceListBox.AddString(entry);
    }
}

解决方案设计说明

1. 设备监控核心类 (CDeviceMonitor)

  • 设备注册:使用RegisterDeviceNotification注册设备通知

  • 设备枚举:通过SetupDiGetClassDevsSetupDiEnumDeviceInterfaces枚举已连接设备

  • 设备信息获取:使用SetupDiGetDeviceRegistryProperty获取设备详细信息

  • 线程安全:使用CCriticalSection保护设备列表

2. 消息处理流程