C++ Win32 在 WM_DEVICECHANGE 上未收到 DBT_DEVICEARRIVAL 或 DBT_DEVICEREMOVECOMPLETE

C++ Win32 Not receiving DBT_DEVICEARRIVAL or DBT_DEVICEREMOVECOMPLETE on WM_DEVICECHANGE

我一直致力于检测 USB insertion/removal。我已经使用 CreateWindowEx() 实现了代码,通过我的 window 进程回调传递了一个 WNCLASSEX。在插入和移除 USB 时,我成功收到 WM_DEVICECHANGE 消息,但 wParam 始终设置为 DBT_DEVNODES_CHANGED。

我从来没有得到 DBT_DEVICEARRIVAL 或 DBT_DEVICEREMOVECOMPLETE。我一直在使用我得到的东西,但我真的需要能够区分设备到达和移除之间的区别,以便我可以根据收到的设备采取不同的操作。

现在,我必须在收到 DBT_DEVNODES_CHANGED 后设置一个计时器,然后测试系统上是否有任何新的可移动设备,或者我的列表中是否不再存在。我确定这是不对的,所以我想我会问。我宁愿摆脱计时器而只接收这两条消息。这对我必须做的事情有很大帮助。有什么建议吗?

这是我用于注册回调的代码,以及回调本身:

注意:2015 年 3 月 12 日:更新代码以显示实际 GUID 和 DoRegisterDeviceInterfaceToHwnd() 函数的定义。:

GUID WceusbshGUID = { 0x25dbce51, 0x6c8f, 0x4a72, 0x8a,0x6d,0xb5,0x4c,0x2b,0x4f,0xc8,0x35 };
//GUID WusbrawGUID = {0xa5dcbf10, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed };
//GUID WusbGUID = {0x88BAE032, 0x5A81, 0x49f0, 0xBC, 0x3D, 0xA4, 0xFF, 0x13, 0x82, 0x16, 0xD6 };

INT_PTR WINAPI WinProcCallback(HWND __hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL DoRegisterDeviceInterfaceToHwnd(GUID InterfaceClassGuid, HWND __hWnd, HDEVNOTIFY *hDeviceNotify);

bool UsbController::startNotifyUsbAddedRemoved(QString &errmsg)
{
    WNDCLASSEX wndClass;

    wndClass.cbSize = sizeof(wndClass);
    wndClass.style = 0;
    wndClass.hInstance = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
    wndClass.lpfnWndProc = reinterpret_cast<WNDPROC>(WinProcCallback);
    wndClass.cbClsExtra = 0;
    wndClass.cbWndExtra = 0;
    wndClass.hIcon = LoadIcon(0, IDI_APPLICATION);
    wndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wndClass.hCursor = LoadCursor(0, IDC_ARROW);
    wndClass.lpszClassName = WND_CLASS_NAME;
    wndClass.lpszMenuName = NULL;
    wndClass.hIconSm = LoadIcon(0, IDI_APPLICATION);

    if (!RegisterClassEx(&wndClass))
    {
        FormatErrorMsg("RegisterClassEx: ", errmsg);
        return false;
    }

    HINSTANCE hInstance = (HINSTANCE)::GetModuleHandle(NULL);
    __hWnd = CreateWindowEx(
                    WS_EX_CLIENTEDGE | WS_EX_APPWINDOW,
                    WND_CLASS_NAME,
                    WND_APP_NAME,
                    WS_OVERLAPPEDWINDOW, // style
                    CW_USEDEFAULT, 0,
                    0, 0,
                    NULL, NULL,
                    hInstance,
                    NULL);

    if ( __hWnd == NULL )
    {
        FormatErrorMsg("CreateWindowEx: ", errmsg);
        return false;
    }

    return true;
}

INT_PTR WINAPI WinProcCallback(HWND __hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT lRet = 1;
    static HDEVNOTIFY hDeviceNotify;
    static HWND hEditWnd;
    static ULONGLONG msgCount = 0;

    switch (message)
    {
    case WM_CREATE:
        //
        // This is the actual registration., In this example, registration
        // should happen only once, at application startup when the window
        // is created.
        //
        // If you were using a service, you would put this in your main code
        // path as part of your service initialization.
        //
        if ( ! DoRegisterDeviceInterfaceToHwnd( WceusbshGUID, __hWnd, &hDeviceNotify) )
        {
            // Terminate on failure.
            //ErrorHandler(TEXT("DoRegisterDeviceInterfaceToHwnd"));
            ExitProcess(1);
        }

        //
        // Make the child window for output.
        //
        hEditWnd = CreateWindow(TEXT("EDIT"),// predefined class
                                NULL,        // no window title
                                WS_CHILD | WS_VISIBLE | WS_VSCROLL |
                                ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL,
                                0, 0, 0, 0,  // set size in WM_SIZE message
                                __hWnd,        // parent window
                                (HMENU)1,    // edit control ID
                                (HINSTANCE) GetWindowLong(__hWnd, GWL_HINSTANCE),
                                NULL);       // pointer not needed

        if ( hEditWnd == NULL )
        {
            // Terminate on failure.
            ExitProcess(1);
        }
        // Add text to the window.
        SendMessage(hEditWnd, WM_SETTEXT, 0,
            (LPARAM)TEXT("Registered for USB device notification...\n"));

        break;

    case WM_SETFOCUS:
        SetFocus(hEditWnd);

        break;

    case WM_SIZE:
        // Make the edit control the size of the window's client area.
        MoveWindow(hEditWnd,
                   0, 0,                  // starting x- and y-coordinates
                   LOWORD(lParam),        // width of client area
                   HIWORD(lParam),        // height of client area
                   TRUE);                 // repaint window

        break;

    case WM_DEVICECHANGE:
        {
            //
            // This is the actual message from the interface via Windows messaging.
            // This code includes some additional decoding for this particular device type
            // and some common validation checks.
            //
            // Note that not all devices utilize these optional parameters in the same
            // way. Refer to the extended information for your particular device type
            // specified by your GUID.
            //

            // Output some messages to the window.
            UsbController *pusbctl;
            switch (wParam)
            {
            case DBT_DEVICEARRIVAL:
                msgCount++;
                pusbctl = UsbController::instance();
                pusbctl->signalDeviceArrival();
                break;
            case DBT_DEVICEREMOVECOMPLETE:
                msgCount++;
                pusbctl = UsbController::instance();
                pusbctl->signalDeviceRemoval();
                break;
            case DBT_DEVNODES_CHANGED:
                msgCount++;
                pusbctl = UsbController::instance();
                pusbctl->signalDeviceAddedRemoved();
                break;
            default:
                msgCount++;
                break;
            }
        }
        break;

    default:
        // Send all other messages on to the default windows handler.
        lRet = DefWindowProc(__hWnd, message, wParam, lParam);
        break;
    }

    return lRet;
}

BOOL DoRegisterDeviceInterfaceToHwnd(GUID InterfaceClassGuid, HWND __hWnd, HDEVNOTIFY *hDeviceNotify)
{
    DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;

    ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
    NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    //NotificationFilter.dbcc_devicetype = DEVICE_NOTIFY_ALL_INTERFACE_CLASSES;
    NotificationFilter.dbcc_classguid = InterfaceClassGuid;

    *hDeviceNotify = RegisterDeviceNotification(
        __hWnd,                       // events recipient
        &NotificationFilter,        // type of device
        DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle
        );

    if ( NULL == *hDeviceNotify )
    {
        return FALSE;
    }

    return TRUE;
}

如果您阅读 MSDN 的文档,它会说:

Detecting Media Insertion or Removal

Windows sends all top-level windows a set of default WM_DEVICECHANGE messages when new devices or media (such as a CD or DVD) are added and become available, and when existing devices or media are removed. You do not need to register to receive these default messages. See the Remarks section in RegisterDeviceNotification for details on which messages are sent by default.

RegisterDeviceNotification function

Any application with a top-level window can receive basic notifications by processing the WM_DEVICECHANGE message. Applications can use the RegisterDeviceNotification function to register to receive device notifications.
...
The DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE events are automatically broadcast to all top-level windows for port devices. Therefore, it is not necessary to call RegisterDeviceNotification for ports, and the function fails if the dbch_devicetype member is DBT_DEVTYP_PORT.

DEV_BROADCAST_HDR structure

DBT_DEVTYP_PORT
0x00000003

Port device (serial or parallel). This structure is a DEV_BROADCAST_PORT structure.

USB 设备不是 serial/parallel 端口。它是一个设备接口(DBT_DEVTYP_DEVICEINTERFACE)。 DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE 不会发送给 DBT_DEVTYP_DEVICEINTERFACE 台设备 默认 。如果你想要它们,你必须使用 RegisterDeviceNotification() 来请求它们。

看起来您的代码是基于这个 MSDN 示例:

Registering for Device Notification

在该代码中,WceusbshGUID 被定义为 {25dbce51-6c8f-4a72-8a6d-b54c2b4fc835},它被注释为 USB 串行主机 PnP 驱动程序的 class guid.根据这个 MSDN 页面:

System-Defined Device Setup Classes Available to Vendors

那个 guid 是 Windows CE USB ActiveSync Devices 的 class guid(与代码)。在同一页面上还有 {88BAE032-5A81-49f0-BC3D-A4FF138216D6} 用于 USB 设备所有 不属于另一个 class).

以下 CodeProject 文章:

Detecting Hardware Insertion and/or Removal

提及 {a5dcbf10-6530-11d2-901f-00c04fb951ed} USB 原始设备。同样的 guid 在 MSDN 上记录为 GUID_DEVINTERFACE_USB_DEVICE (the naming of which probably stems back to pre-XP days when the naming of class guids and interface guids was not well separated).

因此,当您使用 特定 class guid 调用 RegisterDeviceNotification() 时,请确保它是 正确的 class guid,因为您将只获取 特定 类型设备的设备事件。很可能您的 USB 设备使用的 class GUID 与您注册的不同,这就是您没有收到预期的设备事件的原因。

如果你想检测任何 USB设备而不考虑其class guid(并且定义了多个USB class guid),你可以使用调用 RegisterDeviceNotification() 时的 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES 标志,然后 class guid 将被忽略。在 DBT_DEVICEARRIVALDBT_DEVICEREMOVECOMPLETE 消息中(假设您现在可以得到它们),报告的 dbcc_classguid 将告诉您实际的 class guid,报告的 dbcc_name 将以 \?\USB: 前缀开头。

最后一件事 - 如果 USB 设备 inserted/removed 而您的应用程序已经 运行,您只会收到 DBT_DEVICE... 消息。要在您的应用程序启动时检测 USB 设备是否已插入,您必须使用 SetupAPI 函数(SetupDiGetClassDevs()SetupDiEnumDeviceInterfaces()SetupDiGetDeviceInterfaceDetail() 等)来枚举可用的设备。