C++ Win32 Not receiving DBT_DEVICEARRIVAL or DBT_DEVICEREMOVECOMPLETE on WM_DEVICECHANGE
Asked Answered
R

1

12

I have been working on detecting USB insertion/removal. I have implemented code using CreateWindowEx(), passing a WNCLASSEX that with my window process callback. On inserting and removing my usb, I successfully receive the WM_DEVICECHANGE message, but the wParam is always set to DBT_DEVNODES_CHANGED.

I never get either DBT_DEVICEARRIVAL or DBT_DEVICEREMOVECOMPLETE. I have been using what I am getting, but I really need to be able to tell the difference between device arrival and removal, so that I can take different actions depending on which I receive.

Right now, I'm having to put a timer after receiving DBT_DEVNODES_CHANGED, and then test to see if there are any new removables on the system, or if any in my list are no longer there. I'm sure this isn't right, so I thought I'd ask. I would much rather to get rid of the timer and just receive these two messages. That would help a lot in what I have to do. Any suggestions?

Here is the code that I have for registering the callback, as well as the callback itself:

NOTE: 3/12/2015: Code updated to show actual GUID and the definition of the DoRegisterDeviceInterfaceToHwnd() function.):

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;
}
Requiem answered 11/3, 2015 at 22:30 Comment(3)
Did you see msdn.microsoft.com/en-us/library/windows/desktop/… ? It says you don't actually need to register to get media arrival notifications. I wonder if the fact that you are registering might be interfering in some way.Compass
Hmm, my crystal ball says that you are doing this the programmer's way, just jerking out the device without going through the "Safely Remove Hardware" spiel. And then hope that Windows can second-guess what might have happened. Well, it did, it told you about it.Interdict
Actually, I definitely did the "Safely Remove Hardware" process. The notification that I do get there has a value of 7, which is DBT_DEVNODES_CHANGED. I get a number of messages coming from that one action (not yet having pulled the device from its slot), all of which have a value of 7. Of course, when I pull the device, I get the same value. Inserting the device has the same exact results. I get a number of messages, and all of them have the value 7 for DBT_DEVNODES_CHANGED.Requiem
P
15

If you read MSDN's documentation, it says:

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.

A USB device is not a serial/parallel port. It is a Device Interface (DBT_DEVTYP_DEVICEINTERFACE) instead. And DBT_DEVICEARRIVAL/DBT_DEVICEREMOVECOMPLETE are not sent for DBT_DEVTYP_DEVICEINTERFACE devices by default. If you want them, you have to use RegisterDeviceNotification() to request them.

It looks like your code is based on this MSDN example:

Registering for Device Notification

In that code, WceusbshGUID is defined as {25dbce51-6c8f-4a72-8a6d-b54c2b4fc835}, which is commented as being the class guid for USB serial host PnP drivers. According to this MSDN page:

System-Defined Device Setup Classes Available to Vendors

That guid is the class guid for Windows CE USB ActiveSync Devices (which is more consistent with the Wceusb... prefix used in the code). Also on that same page is {88BAE032-5A81-49f0-BC3D-A4FF138216D6} for USB Device (all USB devices that do not belong to another class).

The following CodeProject article:

Detecting Hardware Insertion and/or Removal

Mentions {a5dcbf10-6530-11d2-901f-00c04fb951ed} for USB Raw Device. That same guid is documented on MSDN as 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).

So when you call RegisterDeviceNotification() with a specific class guid, make sure it is the correct class guid, as you are going to get device events for only that specific type of device. It is likely that your USB device is using a different class guid than the one you are registering, and that is why you are not getting the device events you are expecting.

If you want to detect any USB device regardless of its class guid (and there are several USB class guids defined), you can use the DEVICE_NOTIFY_ALL_INTERFACE_CLASSES flag when calling RegisterDeviceNotification(), then the class guid will be ignored. In the DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE messages (assuming you are able to get them now), the reported dbcc_classguid will tell you the actual class guid, and the reported dbcc_name will begin with the \\?\USB: prefix.

One last thing - you are only going to get DBT_DEVICE... messages if a USB device is inserted/removed while your app is already running. To detect if a USB device is already plugged in when your app starts, you have to use SetupAPI functions (SetupDiGetClassDevs(), SetupDiEnumDeviceInterfaces(), SetupDiGetDeviceInterfaceDetail(), etc) to enumerate the available devices.

Possibility answered 12/3, 2015 at 2:18 Comment(6)
Yes I'm sorry, I seem to have left out the definitions for the GUID I used, and for the call to DoRegisterDeviceInterfaceToHwnd(). I just added them back in there.Requiem
Did you try the other class guids I gave you?Possibility
Done and tested. By your suggestions, I ended up changing the call to RegisterDeviceNotification() so that I pass DEVICE_NOTIFY_ALL_INTERFACE_CLASSES as the flag, instead of DEVICE_NOTIFY_WINDOW_HANDLE. After that, I was able to get a non-NULL value for lParam. That gave me paramGuid and paramName, both of which were very useful in telling me what I am actually looking at, so I can make sure this is a USB drive, rather than some other pluggable device such as a security dongle. Thanks for the pointers.Requiem
Sorry I didn't respond earlier, but I spent the morning yesterday working on your suggestions until I tried this one, and then I spent the rest of the day getting this working for my needs. Thanks again.Requiem
Oh, and, per the reason I asked this question, I am getting DBT_DEVICEARRIVAL and DBT_DEVICEREMOVECOMPLETE messages now, so now I can tell when a USB was added/removed, and I can tell whether it's a drive or some other device.Requiem
@RemyLebeau, thank you for the research and writeup. For anyone wanting a complete-ish sample of how all this works together, I have added a demo here - https://mcmap.net/q/1009629/-any-windows-api-for-detecting-mtp-devicesLoveinidleness

© 2022 - 2024 — McMap. All rights reserved.