使用 C++ 在 Windows 中检测 USB 插入/移除事件

Posted

技术标签:

【中文标题】使用 C++ 在 Windows 中检测 USB 插入/移除事件【英文标题】:Detecting USB Insertion / Removal Events in Windows using C++ 【发布时间】:2011-05-03 23:54:04 【问题描述】:

我正在为需要处理 USB 插入/移除事件的现有应用程序编写扩展。我知道感兴趣设备的 VID/PID。但是,我无法访问窗口句柄,所以我不知道RegisterDeviceNotification 是否会有很大用处,除非有办法通过WINAPI 获取句柄。使用 C++ 检测 USB 插入/移除事件的最佳方法是什么?

This sample code on the Microsoft website 展示了如何通过 WMI 接收事件通知:

如何修改它以接收 USB 插入/移除事件?或者,我还有其他方法吗?我正在使用 Visual Studio 2008。谢谢。

附加信息

这是我目前所拥有的(减去错误处理):

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);

PC 进入WndProc,但当我移除/插入我的 USB 设备时没有。 PC 似乎永远不会进入OnDeviceChange。任何提示将不胜感激。我需要处理 USB 设备的意外插入/移除。如果有所不同,USB 设备将显示为 Windows 的虚拟 COM 端口。谢谢。

附加信息:使用 RegisterClassEx 返回的类 atom 调用 CreateWindowEx 失败,并显示错误消息“找不到窗口类”。

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

新方法

我也在尝试这种新方法。我正在尝试编写一个仅消息窗口来接收 USB 设备的设备更改通知消息。我正在使用 MFC、C++ 和 Visual Studio 2008。一切都可以编译,并且运行时不会崩溃或锁定,但永远不会触发事件处理程序。感兴趣的设备作为虚拟 COM 端口安装在 Windows 上。

我的主应用程序实例化下面描述的类,然后使用 while 循环等待来自键盘轮询的字符输入。正是在这段等待时间内,我移除并插入了我的 USB 设备,期望事件被触发。

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.
;

为简单起见,我删除了所有清理和错误处理:

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 );

任何想法或建议将不胜感激。如果缺少任何细节,请告诉我,我很乐意添加它们。谢谢。

仅消息窗口是否需要在新线程中启动,还是创建新窗口会自动派生新线程?

【问题讨论】:

为什么无法访问窗口句柄?您是否将其作为服务运行? @Scott Chamberlain:这是一个多年来一直在修补的应用程序。 GUI 是 Java,其下是 C# 层,其下是一堆 C++ DLL。这就是我工作的地方,修改一些 C++ 并编写一些新的 DLL。我认为窗口句柄可能属于java.exe,但我不确定。也就是说,至少是我附加到的用于在正在运行的应用程序中测试我的 DLL 的进程(MSVS2008->Tools->附加到进程)。 【参考方案1】:

创建一个只等待WM_DEVICECHANGE 的虚拟窗口并使用RegisterDeviceNotification 注册该窗口。恕我直言,WMI 在这里有点矫枉过正。

【讨论】:

谢谢。我添加了一些额外的信息。您可能有的任何其他想法都会有所帮助。 您确定您的界面 GUID 正确吗?你用的是USB2UART芯片。您还跳过了 dbcc_name。我会选择 DEV_BROADCAST_VOLUME 作为初学者,以确保一切正常。您可以使用密钥盘轻松测试卷插入。 @kichik:是的,我相当确定 GUID 是正确的。接口芯片是 Silicon Labs 的 CP2103。他们网站上的知识库特别指出了这一点。我用的是他们的开发板,所以这个设备没有逻辑卷;它只是关闭了 USB 端口。 好的。您仍然可以将 DEV_BROADCAST_VOLUME 与键式磁盘/闪存驱动器一起使用,以确保消息传递代码正常工作。 您可能会使用 DBT_DEVTYP_DEVICEINTERFACE。 DBT_DEVTYP_PORT 可能不会显示,具体取决于您的设备。【参考方案2】:

有一个MSDN sample 专门针对您的情况,采用本机代码。

注册设备通知

这种方式比通过 WMI 更好。

【讨论】:

谢谢。我添加了一些额外的信息。您可能有的任何其他想法都会有所帮助。 @Jim Fell - 您发布的代码从不调用OnDeviceChange。当您看到WM_DEVICECHANGE 时,您的意思是在WndProc 中调用它吗?请注意,示例代码不会无条件调用DefWindowProc,而是在default: 情况下执行此操作 @Steve Townsend:您是说对DefWindowProc 的调用应该来自defaultswitch 语句?我会按照你的建议添加OnDeviceChange,看看情况如何。谢谢。 我不知道 Windows 消息处理,我只是继续示例代码的作用。但如果这是正确的,那么“是”。您处理您关心的消息,然后将其余消息传递给DefWindowProc @Steve Townsend:我明白了。谢谢你。我认为出于此应用程序的目的,我不一定要覆盖DefWindowProc;为了我的应用程序,我想处理某些消息。我确实执行了您关于OnDeviceChange 的建议;这是有道理的。【参考方案3】:

我遵循了您的“新方法”,还发现没有调用 OnDeviceChange。问题是没有消息循环,因为它是一个控制台应用程序。定期调用以下函数修复它。

void check_for_device_change()

    MSG msg; 

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

    if( val > 0 )
     
        TranslateMessage( &msg );
        DispatchMessage( &msg );
     

【讨论】:

如果您在使用此代码时遇到奇怪的链接器错误,请参阅***.com/q/69004409/4284627。【参考方案4】:

这是另一种检测 USB 存储设备插入和移除的方法。

此 c++ 代码检测 USB 存储设备的 INSERTION 和 REMOVAL。

这还可以检测同时插入和移除多个 USB 设备。

c++ 代码:在 VISUAL STUDIO 2015 中测试

您还可以检查其他类型的设备以进行移除和插入。 只需在函数getUSBStorageDeviceList()

中的代码if else中填充传递的char数组到其他类型的设备
    #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;
    

备注:这不会创建任何窗口。这是控制台应用程序。

【讨论】:

以上是关于使用 C++ 在 Windows 中检测 USB 插入/移除事件的主要内容,如果未能解决你的问题,请参考以下文章

从 USB 存储器中检索序列号(Windows 环境 c++)

如何使用python检测USB设备是不是插入?

USB 令牌 - *.dll 检测

在 Visual C++ (Windows) 中检测内存泄漏

Windows 下 Quartus 检测不到 USB-Blaster 终极解决办法

Windows和Linux中USB串口的区别