Qt在Windows上的设备热插拔检测

一般 Qt 在 Windows 上做设备热插拔需要两个步骤:

1.使用 win32 的 RegisterDeviceNotification 函数注册要监听的设备类别,需要绑定一个窗口 id;

2.重写 QWidget 的 nativeEvent 虚函数或者 QAbstractNativeEventFilter 的 nativeEventFilter 虚函数,处理热插拔相关的回调。

后来参考别人的代码,可以创建一个 win32 的隐藏窗口来接收消息,这样就不用耦合到 Qt 界面上的窗口了。

(2023-03-21 补充)

如果用通用的 USB GUID GUID_DEVINTERFACE_USB_DEVICE 来注册,插入 UVC 相机并触发插入消息后,去枚举相机列表,此时可能还枚举不到这个设备,如果注册的  KSCATEGORY_CAPTURE 就能检测到插入的 UVC 相机。

GUID_DEVINTERFACE_USB_DEVICE -- usbiodef.h
GUID{ 0xA5DCBF10L, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED } }
KSCATEGORY_CAPTURE -- Ks.h
GUID{ 0x65E8773DL, 0x8F56, 0x11D0, { 0xA3, 0xB9, 0x00, 0xA0, 0xC9, 0x22, 0x31, 0x96 } }

代码链接:

https://github.com/gongjianbo/MyTestCode/tree/master/Qt/DeviceHotplug_Win

主要代码:

main.cpp

#include <QApplication>
#include <QMainWindow>
#include <QDebug>
#include "DeviceHotplug.h"

#include <Windows.h>
#include <Dbt.h>
#include <devguid.h>
// 具体的设备 GUID 需要 initguid, 如 usbiodef
#include <initguid.h>
// USB 设备
// GUID_DEVINTERFACE_USB_DEVICE
#include <usbiodef.h>
// HID 人机交互设备-鼠标键盘等
#include <hidclass.h>
// 键盘 GUID_DEVINTERFACE_KEYBOARD
#include <ntddkbd.h>
// 鼠标 GUID_DEVINTERFACE_MOUSE
#include <ntddmou.h>

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    DeviceHotplug filter;
    QVector<QUuid> uuids;
    // 监测 USB 插拔
    uuids << GUID_DEVINTERFACE_USB_DEVICE;
    // 监测鼠标键盘插拔,有些有扩展功能的鼠标会同时触发键盘和鼠标的插拔事件
    // uuids << GUID_DEVINTERFACE_KEYBOARD << GUID_DEVINTERFACE_MOUSE;
    filter.init(uuids);
    QObject::connect(&filter, &DeviceHotplug::deviceAttached,
                     &app, [](quint16 vid, quint16 pid){
        qDebug()<<"attached"<<QString::number(vid, 16)<<QString::number(pid, 16);
    });
    QObject::connect(&filter, &DeviceHotplug::deviceDetached,
                     &app, [](quint16 vid, quint16 pid){
        qDebug()<<"detached"<<QString::number(vid, 16)<<QString::number(pid, 16);
    });

    QMainWindow w;
    w.resize(600, 500);
    w.setWindowTitle("DeviceHotplug (by GongJianBo 1992)");
    w.show();

    return app.exec();
}

 DeviceEventFilter.h

扫描二维码关注公众号,回复: 14846581 查看本文章
#pragma once
#include <QObject>
#include <QUuid>
#include <QSharedPointer>
#include <Windows.h>
class DeviceHotplugPrivate;

/**
 * @brief 设备插拔事件监听
 * @author 龚建波
 * @date 2022-12-24
 * @details
 * 设备类文档,可以在设备管理器找自己的设备 GUID
 * https://learn.microsoft.com/zh-cn/windows-hardware/drivers/install/overview-of-device-setup-classes
 * @history
 * 2023-03-20
 * 参考 https://github.com/wang-bin/qdevicewatcher
 * 由 QAbstractNativeEventFilter 过滤事件改为了创建一个 win32 窗口来接收事件
 */
class DeviceHotplug : public QObject
{
    Q_OBJECT
public:
    explicit DeviceHotplug(QObject *parent = nullptr);
    ~DeviceHotplug();

    // RegisterDeviceNotification 注册对应的 GUID 消息通知
    // 暂未考虑重复注册和注册失败的处理
    void init(const QVector<QUuid> &uuids);

    // UnregisterDeviceNotification
    // 会在析构中自动调用一次
    void free();

signals:
    // 设备插入
    void deviceAttached(quint16 vid, quint16 pid);
    // 设备拔出
    void deviceDetached(quint16 vid, quint16 pid);

private:
    QSharedPointer<DeviceHotplugPrivate> dptr;
};

 DeviceEventFilter.cpp

#include "DeviceHotplug.h"
#include <QMetaObject>
#include <QHash>
#include <QDebug>
#include <Dbt.h>
#pragma comment(lib, "user32.lib")

// 内部结构,存储对应平台需要的数据
class DeviceHotplugPrivate
{
public:
    void deviceAttached(quint16 vid, quint16 pid) {
        QMetaObject::invokeMethod(ptr, "deviceAttached", Qt::QueuedConnection,
                                  Q_ARG(quint16, vid),
                                  Q_ARG(quint16, pid));
    }
    void deviceDetached(quint16 vid, quint16 pid) {
        QMetaObject::invokeMethod(ptr, "deviceDetached", Qt::QueuedConnection,
                                  Q_ARG(quint16, vid),
                                  Q_ARG(quint16, pid));
    }
    // 处理窗口消息
    static LRESULT CALLBACK windowMessageProcess(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
    // win32 窗口类名
    static QString windowClassName();
    // 创建一个用于接收消息的窗口,注册消息回调
    bool createMessageWindow(const QVector<QUuid> &uuids);
    // 释放
    void destroyMessageWindow();

    // 关联的对象
    DeviceHotplug *ptr{nullptr};
    // 关联的窗口
    HWND hwnd{nullptr};
    // 设备通知句柄和 UUID/GUID
    QHash<QUuid, HDEVNOTIFY> devNotifys;
};

// 处理窗口消息
LRESULT CALLBACK DeviceHotplugPrivate::windowMessageProcess(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_DEVICECHANGE) {
        do {
            // 设备可用事件
            const bool is_add = wParam == DBT_DEVICEARRIVAL;
            // 设备移除事件
            const bool is_remove = wParam == DBT_DEVICEREMOVECOMPLETE;
            if (!is_add && !is_remove)
                break;
            // 过滤 device interface class 以外类型的消息
            DEV_BROADCAST_HDR *broadcast = reinterpret_cast<DEV_BROADCAST_HDR *>(lParam);
            if (!broadcast || broadcast->dbch_devicetype != DBT_DEVTYP_DEVICEINTERFACE)
                break;
            // 获取 SetWindowLongPtrW 设置的对象
            DeviceHotplugPrivate *data = reinterpret_cast<DeviceHotplugPrivate *>(::GetWindowLongPtrW(hwnd, GWLP_USERDATA));
            if (!data)
                break;
            // 过滤不监听的设备类型
            DEV_BROADCAST_DEVICEINTERFACE *device_interface = reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE *>(broadcast);
            QUuid uid(device_interface->dbcc_classguid);
            if (!data->devNotifys.contains(uid))
                break;
            QString device_name;
            if (device_interface->dbcc_name) {
#ifdef UNICODE
                device_name = QString::fromWCharArray(device_interface->dbcc_name);
#else
                device_name = QString(device_interface->dbcc_name);
#endif
            }
            // 从设备描述中获取 vid 和 pid
            int offset = -1;
            quint16 vid = 0;
            quint16 pid = 0;
            offset = device_name.indexOf("VID_");
            if (offset > 0 && offset + 8 <= device_name.size()) {
                vid = device_name.mid(offset + 4, 4).toUShort(nullptr, 16);
            }
            offset = device_name.indexOf("PID_");
            if (offset > 0 && offset + 8 <= device_name.size()) {
                pid = device_name.mid(offset + 4, 4).toUShort(nullptr, 16);
            }
            if (is_add) {
                fprintf(stderr, "device attached: vid 0x%04x, pid 0x%04x.\n", vid, pid);
                data->deviceAttached(vid, pid);
            } else if (is_remove) {
                fprintf(stderr, "device detached: vid 0x%04x, pid 0x%04x.\n", vid, pid);
                data->deviceDetached(vid, pid);
            }
        } while(false);
    }

    return ::DefWindowProcW(hwnd, message, wParam, lParam);
}

// 窗口类名
QString DeviceHotplugPrivate::windowClassName()
{
    return QLatin1String("Qt_DeviceHotplug_Window_") + QString::number(quintptr(windowMessageProcess));
}

// 创建一个用于接收消息的窗口,注册消息回调
bool DeviceHotplugPrivate::createMessageWindow(const QVector<QUuid> &uuids)
{
    QString class_name = windowClassName();
    HINSTANCE hi = ::GetModuleHandleW(nullptr);

    WNDCLASSW wc;
    memset(&wc, 0, sizeof(WNDCLASSW));
    wc.lpfnWndProc = windowMessageProcess;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hi;
    wc.lpszClassName = reinterpret_cast<const wchar_t *>(class_name.utf16());
    ::RegisterClassW(&wc);

    hwnd = ::CreateWindowW(wc.lpszClassName, // classname
                           wc.lpszClassName, // window name
                           0,  // style
                           0,  // x
                           0,  // y
                           0,  // width
                           0,  // height
                           0,  // parent
                           0,  // menu handle
                           hi, // application
                           0); // windows creation data.
    if (!hwnd) {
        qDebug()<<"createMessageWindow error"<<(int)GetLastError();
    } else {
        // 初始化 DEV_BROADCAST_DEVICEINTERFACE 数据结构
        DEV_BROADCAST_DEVICEINTERFACE_W filter_data;
        memset(&filter_data, 0, sizeof(DEV_BROADCAST_DEVICEINTERFACE_W));
        filter_data.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE_W);
        filter_data.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
        for (auto &&uuid : uuids)
        {
            filter_data.dbcc_classguid = uuid;
            HDEVNOTIFY handle = ::RegisterDeviceNotificationW(hwnd, &filter_data, DEVICE_NOTIFY_WINDOW_HANDLE);
            if (handle) {
                devNotifys.insert(uuid, handle);
            } else {
                qDebug()<<"RegisterDeviceNotification error"<<uuid;
            }
        }
        ::SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)this);
    }
    return !!hwnd;
}

// 释放
void DeviceHotplugPrivate::destroyMessageWindow()
{
    if (hwnd) {
        ::DestroyWindow(hwnd);
        hwnd = nullptr;

        for (HDEVNOTIFY handle : qAsConst(devNotifys))
        {
            ::UnregisterDeviceNotification(handle);
        }
        devNotifys.clear();
    }
    ::UnregisterClassW(reinterpret_cast<const wchar_t *>(windowClassName().utf16()), ::GetModuleHandleW(nullptr));
}

DeviceHotplug::DeviceHotplug(QObject *parent)
    : QObject{parent}
    , dptr{new DeviceHotplugPrivate}
{
    dptr->ptr = this;
}

DeviceHotplug::~DeviceHotplug()
{
    free();
}

void DeviceHotplug::init(const QVector<QUuid> &uuids)
{
    const bool ret = dptr->createMessageWindow( uuids);
    if (!ret) {
        dptr->destroyMessageWindow();
    }
}

void DeviceHotplug::free()
{
    dptr->destroyMessageWindow();
}

猜你喜欢

转载自blog.csdn.net/gongjianbo1992/article/details/128702423
今日推荐