25

I am writing an extension for an existing application that needs to handle USB insertion/removal events. I know the VID/PID of the device of interest. However, I don't have access to the window handle, so I don't know if RegisterDeviceNotification will be of much use, unless there is a way to obtain the handle via the WINAPI. What would be the best way to detect USB insertion/removal events with C++?

This sample code on the Microsoft website shows how to receive event notifications via WMI:

How could it be modified to receive USB insertion/removal events? Or, is there another way I should be going about this? I am using Visual Studio 2008. Thanks.

ADDITIONAL INFO

This is what I have so far (minus error-handling):

DEFINE_GUID(GUID_INTERFACE_CP210x, 0x993f7832, 0x6e2d, 0x4a0f, 0xb2, 0x72, 0xe2, 0xc7, 0x8e, 0x74, 0xf9, 0x3e);

MyClass::MyClass()
{
    // Generate message-only window
    _pWndClassEx = (WNDCLASSEX *)malloc( sizeof(WNDCLASSEX) );
    memset( _pWndClassEx, 0, sizeof(WNDCLASSEX) );
    _pWndClassEx->cbSize = sizeof(WNDCLASSEX);
    _pWndClassEx->lpfnWndProc = (WNDPROC)WndProc; // function which will handle messages
    _pWndClassEx->hInstance = GetCurrentModule();
    _pWndClassEx->lpszClassName = pClassName;
    atom = RegisterClassEx( _pWndClassEx );
    _hWnd = CreateWindowEx( 0, pClassName, pWindowName, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL );

    // Register the USB device for notification
    _pDevIF = (DEV_BROADCAST_DEVICEINTERFACE *)malloc( sizeof(DEV_BROADCAST_DEVICEINTERFACE) );
    memset( _pDevIF, 0, sizeof(DEV_BROADCAST_DEVICEINTERFACE) );
    _pDevIF->dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    _pDevIF->dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    _pDevIF->dbcc_classguid = GUID_INTERFACE_CP210x;
    _hNotifyDevNode = RegisterDeviceNotification( _hWnd, _pDevIF, DEVICE_NOTIFY_WINDOW_HANDLE );
}

static bool OnDeviceChange(UINT nEventType, DWORD dwData)
{
    switch ( nEventType )
    {
    case DBT_DEVICEARRIVAL:
        // A device has been inserted adn is now available.
        break;

    case DBT_DEVICEREMOVECOMPLETE:
        // Device has been removed.
        break;

    default:
        break;
    }

    return true;
}

static LRESULT WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch ( message )
    {
    case WM_DEVICECHANGE:
        OnDeviceChange( wParam, lParam ); // Set breakpoint (never gets here)
        break;

    default:
        break;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

The PC gets into WndProc, but not when I remove/insert my USB device. The PC never seems to get into OnDeviceChange. Any tips would be appreciated. I need to handle unexpected insertions/removals of the USB device. If it makes a difference, the USB device appears as a virtual COM port to Windows. Thanks.

Aditional info: Calling CreateWindowEx using the class atom returned by RegisterClassEx fails with the error message, "Cannot find window class."

_hWnd = CreateWindowEx( 0, (LPCTSTR)&atom, pWindowName, 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, NULL, NULL );

NEW APPROACH

I'm also trying this new approach. I'm trying to get write a message-only window to receive device change notification messages for a USB device. I am using MFC, C++, and Visual Studio 2008. Everything compiles, and it runs without crashing or locking up, but the event handler is never triggered. The device of interest is installed on Windows as a virtual COM port.

My main application instantiates the class described below then waits for a character input from the keyboard polling using a while loop. It is during this wait time that I remove and insert my USB device expecting the event to get fired.

class CMessageOnlyWindow : public CWnd
{
    DECLARE_DYNAMIC(CMessageOnlyWindow)
private:
    DEV_BROADCAST_DEVICEINTERFACE * _pDevIF; // The notification filter.
    HDEVNOTIFY _hNotifyDev;             // The device notification handle.
public:
    CMessageOnlyWindow();
    virtual ~CMessageOnlyWindow();
protected:
    afx_msg BOOL OnDeviceChange( UINT nEventType, DWORD dwData );
private:
    void RegisterNotification( void );
    void UnregisterNotification( void );
protected:
    DECLARE_MESSAGE_MAP()               // Must be last.
};

For simplicity, I've removed all the cleanup and error-handling:

DEFINE_GUID(GUID_INTERFACE_CP210x, 0x993f7832, 0x6e2d, 0x4a0f, \
    0xb2, 0x72, 0xe2, 0xc7, 0x8e, 0x74, 0xf9, 0x3e);

IMPLEMENT_DYNAMIC(CMessageOnlyWindow, CWnd)

CMessageOnlyWindow::CMessageOnlyWindow()
{
    CString cstrWndClassName = ::AfxRegisterWndClass( NULL );
    BOOL bCreated = this->CreateEx( 0, cstrWndClassName,
        L"CMessageOnlyWindow", 0, 0, 0, 0, 0, HWND_MESSAGE, 0 );
    this->RegisterNotification();
}

CMessageOnlyWindow::~CMessageOnlyWindow() {}

BEGIN_MESSAGE_MAP(CMessageOnlyWindow, CWnd)
    ON_WM_DEVICECHANGE()
END_MESSAGE_MAP()

afx_msg BOOL CMessageOnlyWindow::OnDeviceChange( UINT nEventType, DWORD dwData )
{
    switch ( nEventType ) // <-- Never gets here.
    {
    case DBT_DEVICEARRIVAL:
        break;

    case DBT_DEVICEREMOVECOMPLETE:
        break;

    default:
        break;
    }

    return TRUE;
}

void CMessageOnlyWindow::RegisterNotification(void)
{
    _pDevIF = (DEV_BROADCAST_DEVICEINTERFACE *)malloc( sizeof(DEV_BROADCAST_DEVICEINTERFACE) );
    memset( _pDevIF, 0, sizeof(DEV_BROADCAST_DEVICEINTERFACE) );
    _pDevIF->dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    _pDevIF->dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    _pDevIF->dbcc_classguid = GUID_INTERFACE_CP210x;
    _hNotifyDev = RegisterDeviceNotification( this->m_hWnd, _pDevIF, DEVICE_NOTIFY_WINDOW_HANDLE );
}

void CMessageOnlyWindow::UnregisterNotification(void)
{
    UnregisterDeviceNotification( _hNotifyDev );
}

Any thoughts or suggestions would be much appreciated. If any details are missing, let me know, and I will be glad to add them. Thanks.

Does the message-only window need to be started in a new thread, or does creating a new window automatically spin off a new thread?

skaffman
  • 398,947
  • 96
  • 818
  • 769
Jim Fell
  • 13,750
  • 36
  • 127
  • 202
  • Why do you not have access to the window handle? Are you running this as a service? – Scott Chamberlain Nov 02 '10 at 15:01
  • 2
    @Scott Chamberlain: This is an application that has been patched together over the years. The GUI is Java, under which is a C# layer, and under that is a bunch of C++ DLLs. That's where I'm working, modifying some of the C++ an writing some new DLLs. I think the window handle may belong to `java.exe`, but I'm not sure. That is, at least, the process to which I attach to test my DLL in the running application (MSVS2008->Tools->Attach to process). – Jim Fell Nov 02 '10 at 15:10

4 Answers4

16

Create a dummy window that does nothing but wait for WM_DEVICECHANGE and register that window using RegisterDeviceNotification. WMI is an overkill here, IMHO.

kichik
  • 33,220
  • 7
  • 94
  • 114
  • Thanks. I've added some additional information. Any other ideas you may have would be helpful. – Jim Fell Nov 03 '10 at 20:35
  • Are you sure you got the right interface GUID? The one you've used is for USB2UART chip. You've also skipped dbcc_name. I'd go with DEV_BROADCAST_VOLUME for starters just to make sure everything works. You can easily test volume insertion with a disk-on-key. – kichik Nov 04 '10 at 08:11
  • @kichik: Yes, I'm fairly certain the GUID is correct. The interface chip is a CP2103 from Silicon Labs. The knowledge base on their website specifically calls it out. I'm using their development board, so there is no logical volume for this device; it's just haning off the USB port. – Jim Fell Nov 04 '10 at 15:07
  • OK. You can still use DEV_BROADCAST_VOLUME with a disk-on-key/flash drive to make sure the messaging code is working properly. – kichik Nov 04 '10 at 15:09
  • You will likely be using DBT_DEVTYP_DEVICEINTERFACE. DBT_DEVTYP_PORT may not show up, depending on your device. – Brad Nov 05 '10 at 15:18
  • Changing the type of recipient handle passed to `RegisterDeviceNotification` to `DEVICE_NOTIFY_ALL_INTERFACE_CLASSES` caused it to start working. Do you know why that would make a difference, or are there any problems, if I leave it like this? – Jim Fell Nov 05 '10 at 16:11
  • No problem leaving it like this as long as you filter the messages yourself. But if that change works, then you probably have some issue with the original structure. BTW, as for the new code you posted, you have two problems you're not supposed to send a pointer to the atom, but the atom itself. I'd write the whole thing with pure WinAPI and avoid MFC mess until you get it working. – kichik Nov 05 '10 at 16:18
  • @kichik: Unfortunately, when I tried passing the `atom` itself I got a BSOD. MSDN does specify a LPTR to an atom. http://msdn.microsoft.com/en-us/library/aa930455.aspx Anyways, that was the old approach, in which I was attempting to use the Win32 API. That just got really unmanageable. After finding this article on The Code Project (http://www.codeproject.com/KB/dialog/messageonly.aspx), I thought I'd give it a go with MFC. What do you mean by filtering the messages myself? The PC is getting into `OnDeviceChange` now. Also, what was the other problem you saw? – Jim Fell Nov 05 '10 at 16:37
  • You said you were getting `OnDeviceChange` calls now that you've started using `DEVICE_NOTIFY_ALL_INTERFACE_CLASSES`, so I was suggesting you filter those messages by the GUID in your own function instead of letting Windows filter them for you and find your class. I'd start by printing all the GUID's you get. Maybe it's different than what you assume it should be for some reason. – kichik Nov 06 '10 at 11:32
9

There is a MSDN sample specifically for your case, in native code.

Registering for Device Notification

Better to do it this way than via WMI.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • Thanks. I've added some additional information. Any other ideas you may have would be helpful. – Jim Fell Nov 03 '10 at 20:36
  • @Jim Fell - the code you posted never calls `OnDeviceChange`. Did you mean to call this in `WndProc` when you see `WM_DEVICECHANGE`? note that the sample code does not unconditionally call `DefWindowProc`, it does this in the `default:` case – Steve Townsend Nov 03 '10 at 20:44
  • @Steve Townsend: Are you saying that the call to `DefWindowProc` should be from the `default` case of the `switch` statement? I'll add `OnDeviceChange`, as you suggested and see how it goes. Thanks. – Jim Fell Nov 03 '10 at 20:53
  • I don't know Windows message handling, I am just going on what the sample code did. But if that is correct, then 'yes'. You handle the messages you care about and then pass the rest to `DefWindowProc`. – Steve Townsend Nov 03 '10 at 21:06
  • @Steve Townsend: I see. Thank you. I think for the purposes of this application, I don't neccesarily want to override `DefWindowProc`; I want to handle certain messages for the sake of my application. I did implement your suggestion regarding `OnDeviceChange`; it makes sense. – Jim Fell Nov 03 '10 at 21:11
  • Keep going Jim, this is complex logic but I believe you are on the right track. – Steve Townsend Nov 03 '10 at 21:12
  • This technique is Windowed application only, right? What about a console app? Is creating an invisible window the only option? – kakyo Sep 16 '19 at 03:55
7

I followed your "new approach" and also found that OnDeviceChange wasn't being called. The problem was that there was no message loop because it was a Console app. Calling the following function at regular intervals fixed it.

void check_for_device_change()
{
    MSG msg; 

    const int val = PeekMessage( &msg, 0, 0, 0, PM_REMOVE );

    if( val > 0 )
    { 
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    } 
}
John W
  • 426
  • 1
  • 5
  • 10
1

This is another way for detecting the INSERTION & REMOVAL of USB storage devices.

This c++ code detect INSERTION and REMOVAL both of USB Storage devices.

This also detect Multiple insertion and removal of USB devices at same time.

c++ code: Tested in VISUAL STUDIO 2015

You can also check for other types of devices for removal and insertion. Just fill passed char array to other types of devices in if else of the code in function getUSBStorageDeviceList()

    #include "stdafx.h"
    #include <stdio.h>
    #include <time.h>
    #include <windows.h>
    #include <string>
    #include<iostream>

    using namespace std;

    #define MAX_LETTER 26
    char PREV_DRIVE_LIST[MAX_LETTER];
    char NEW_DRIVE_LIST[MAX_LETTER];

    /* To GET DRIVE LIST in char ARRAY */
    void getUSBStorageDeviceList(char drive[]) {

        int count = 0;

        char szLogicalDrives[MAX_PATH];
        size_t size = strlen(szLogicalDrives) + 1;
        wchar_t* text = new wchar_t[size];

        size_t outSize;
        mbstowcs_s(&outSize, text, size, szLogicalDrives, size - 1);

        DWORD dwResult = GetLogicalDriveStrings(MAX_PATH, text); // text = szLogicalDrives
        WCHAR* szSingleDrive = text;

        while (*szSingleDrive)
        {
            UINT nDriveType = GetDriveType(szSingleDrive);

        //  printf("\nFUNC: getRemovableDisk, Drive Name%d= %s", ++count, szSingleDrive);

            if (nDriveType == DRIVE_UNKNOWN) {
            //  cout << "\nDrive type : Unknown: The drive type cannot be determined." << endl;
            }
            else if (nDriveType == DRIVE_NO_ROOT_DIR) {
            //  cout << "\nDrive type : Invalid Root Directory Media: The root path is invalid." << endl;
            }
            else if (nDriveType == DRIVE_REMOVABLE) {
            //  cout << "\nDrive type :  Removable Media:" << endl;
                char letter = szSingleDrive[0];
                drive[letter - 65] = letter;
            }
            else if (nDriveType == DRIVE_FIXED) {
                //cout << "\nDrive type : Fixed Media: " << endl;
            }
            else if (nDriveType == DRIVE_REMOTE) {
                //cout << "\nDrive type : Remote Media: The drive is a remote (network) drive.." << endl;
            }
            else if (nDriveType == DRIVE_CDROM) {
                //cout << "\nDrive type : CD ROM:   The drive is a CD-ROM drive." << endl;
            }
            else if (nDriveType == DRIVE_RAMDISK) {
                //cout << "\nDrive type : RAM Disk: The drive is a RAM disk." << endl;
            }

            szSingleDrive += wcslen(szSingleDrive) + 1; // next drive 
        }
    }

    int main(void) {

        int count = 0;
        for (int i = 0; i < MAX_LETTER; i++) {
            PREV_DRIVE_LIST[i] = '0';
            NEW_DRIVE_LIST[i] = '0';
        }
        // initial drive list which is already attached 
        getUSBStorageDeviceList(PREV_DRIVE_LIST);

        while (1) {

            getUSBStorageDeviceList(NEW_DRIVE_LIST);
            count = 1;

            /* Check for insertion and removabal*/

            for (int i = 0; i < MAX_LETTER; i++) {
                // check for new drive
                if ((NEW_DRIVE_LIST[i] >= 65 && NEW_DRIVE_LIST[i] <= 89) && (PREV_DRIVE_LIST[i] == '0')) {

                    printf("\nNew Device Inserted%d : %c", count++, NEW_DRIVE_LIST[i]);
                    PREV_DRIVE_LIST[i] = NEW_DRIVE_LIST[i];
                }
            }
                // fill ALl zero 
                for (int i = 0; i < MAX_LETTER; i++) {
                    NEW_DRIVE_LIST[i] = '0';
                }
                // update NEW drive list
                getUSBStorageDeviceList(NEW_DRIVE_LIST);

                for (int i = 0; i < MAX_LETTER; i++) {
                    // check for removed drive
                    if ((PREV_DRIVE_LIST[i] >= 65 && PREV_DRIVE_LIST[i] <= 89) && (NEW_DRIVE_LIST[i] == '0')) {
                        printf("\nDevice Removed%d : %c", count++, PREV_DRIVE_LIST[i]);
                        PREV_DRIVE_LIST[i] = NEW_DRIVE_LIST[i];
                    }
            }
                Sleep(500);
        }

        return 0;
    }

REMARK : This does not create Any windows. This is console Application.

jayprakash
  • 41
  • 3