【问题标题】:C++ Win32 Not receiving DBT_DEVICEARRIVAL or DBT_DEVICEREMOVECOMPLETE on WM_DEVICECHANGEC++ Win32 未在 WM_DEVICECHANGE 上接收 DBT_DEVICEARRIVAL 或 DBT_DEVICEREMOVECOMPLETE
【发布时间】:2015-03-11 22:30:44
【问题描述】:

我一直致力于检测 USB 插入/移除。我已经使用 CreateWindowEx() 实现了代码,通过我的窗口进程回调传递了一个 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.microsoft.com/en-us/library/windows/desktop/…了吗?它说您实际上不需要注册即可获得媒体到达通知。我想知道您正在注册这一事实是否会以某种方式干扰。
  • 嗯,我的水晶球说你正在按照程序员的方式做这件事,只是在没有通过“安全删除硬件”的情况下猛拉设备。然后希望 Windows 可以猜测可能发生的事情。嗯,它确实告诉了你。
  • 实际上,我确实做了“安全删除硬件”过程。我到达那里的通知值为 7,即 DBT_DEVNODES_CHANGED。我从那个操作中收到了许多消息(尚未从插槽中拉出设备),所有这些消息的值都是 7。当然,当我拉出设备时,我得到的值相同。插入设备具有相同的确切结果。我收到了许多消息,所有消息的 DBT_DEVNODES_CHANGED 的值为 7。

标签: c++ winapi usb


【解决方案1】:

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

Detecting Media Insertion or Removal

当新设备或媒体(如 CD 或 DVD)添加并可用时,以及现有设备或媒体被移除时,Windows 会向所有顶级窗口发送一组默认 WM_DEVICECHANGE 消息.您无需注册即可接收这些默认消息。 有关默认发送哪些消息的详细信息,请参阅 RegisterDeviceNotification 中的备注部分

RegisterDeviceNotification function

任何具有顶级窗口的应用程序都可以通过处理 WM_DEVICECHANGE 消息来接收基本通知。应用程序可以使用 RegisterDeviceNotification 函数注册接收设备通知
...
DBT_DEVICEARRIVAL 和 DBT_DEVICEREMOVECOMPLETE 事件自动广播到端口设备的所有顶级窗口。因此,对于ports 不需要调用RegisterDeviceNotification,如果dbch_devicetype 成员是DBT_DEVTYP_PORT,函数就会失败。

DEV_BROADCAST_HDR structure

DBT_DEVTYP_PORT
0x00000003

端口设备(串行或并行)。这个结构是一个 DEV_BROADCAST_PORT 结构。

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

您的代码似乎基于此 MSDN 示例:

Registering for Device Notification

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

System-Defined Device Setup Classes Available to Vendors

该 guid 是 Windows CE USB ActiveSync 设备 的类 guid(与代码中使用的 Wceusb... 前缀更一致)。在同一页面上还有{88BAE032-5A81-49f0-BC3D-A4FF138216D6} 代表USB 设备所有不属于其他类的 USB 设备)。

以下 CodeProject 文章:

Detecting Hardware Insertion and/or Removal

提及{a5dcbf10-6530-11d2-901f-00c04fb951ed} 表示USB 原始设备。相同的 guid 在 MSDN 上记录为 GUID_DEVINTERFACE_USB_DEVICE(其命名可能可以追溯到 XP 之前的时代,当时类 guid 和接口 guid 的命名was not well separated)。

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

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

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

【讨论】:

  • 是的,很抱歉,我似乎遗漏了我使用的 GUID 以及对 DoRegisterDeviceInterfaceToHwnd() 的调用的定义。我刚刚将它们添加回那里。
  • 你试过我给你的其他类指导吗?
  • 完成并经过测试。根据您的建议,我最终更改了对 RegisterDeviceNotification() 的调用,以便将 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES 作为标志传递,而不是 DEVICE_NOTIFY_WINDOW_HANDLE。之后,我能够为 lParam 获得一个非 NULL 值。这给了我 paramGuid 和 paramName,这两者在告诉我我实际在看什么方面非常有用,所以我可以确保这是一个 USB 驱动器,而不是其他一些可插拔设备,例如安全加密狗。谢谢指点。
  • 对不起,我没有早点回复,但我昨天早上花了你的建议,直到我尝试了这个,然后我花了一天剩下的时间来满足我的需要。再次感谢。
  • 哦,根据我问这个问题的原因,我现在收到 DBT_DEVICEARRIVAL 和 DBT_DEVICEREMOVECOMPLETE 消息,所以现在我可以判断何时添加/删除了 USB,并且我可以判断它是否是驱动器或其他设备。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-21
  • 1970-01-01
  • 2011-06-21
  • 2012-04-27
  • 1970-01-01
相关资源
最近更新 更多