在Windows中使用C++检测USB插入/拔出事件

25
我正在为一个现有应用程序编写扩展,需要处理 USB 插入/拔出事件。我知道感兴趣的设备的 VID/PID。但是,我没有访问窗口句柄的权限,所以不知道 RegisterDeviceNotification 是否有用,除非有一种通过 WINAPI 获取句柄的方法。使用 C++ 检测 USB 插入/拔出事件的最佳方法是什么? Microsoft 网站上的此示例代码 显示如何通过 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);
}

我的电脑能够进入WndProc,但是当我移除或插入我的USB设备时却不能。我的电脑似乎永远也无法进入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。所有的编译都通过了,它能够运行而不崩溃或锁定,但是事件处理程序从未被触发。需要注意的是,这个感兴趣的设备在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
2
@Scott Chamberlain:这是一个多年来拼凑起来的应用程序。GUI 是 Java,下面是 C# 层,再下面是一堆 C++ DLL。那就是我正在工作的地方,修改一些 C++ 并编写一些新的 DLL。我认为窗口句柄可能属于 java.exe,但我不确定。也就是说,这是我附加到运行应用程序中测试我的 DLL 的进程(MSVS2008->工具->附加到进程)。 - Jim Fell
4个回答

16
创建一个什么也不做,只是等待 WM_DEVICECHANGE 消息的虚拟窗口,并使用 RegisterDeviceNotification 注册该窗口。在我看来,使用 WMI 过于复杂了。

谢谢。我已经添加了一些额外信息。你有任何其他想法都会很有帮助。 - Jim Fell
你确定使用了正确的接口GUID吗?你使用的那个是针对USB2UART芯片的。你还跳过了dbcc_name。我建议先使用DEV_BROADCAST_VOLUME来确保一切正常。你可以用一个U盘轻松测试卷插入。 - kichik
@kichik:是的,我相当确定GUID是正确的。接口芯片是来自Silicon Labs的CP2103。他们网站上的知识库专门提到了它。我正在使用他们的开发板,因此对于此设备没有逻辑卷;它只是挂在USB端口上。 - Jim Fell
好的。您仍然可以使用DEV_BROADCAST_VOLUME和一个磁盘/闪存驱动器,以确保消息传递代码正常工作。 - kichik
您可能会使用DBT_DEVTYP_DEVICEINTERFACE。根据您的设备的不同,可能不会出现DBT_DEVTYP_PORT。 - Brad
显示剩余4条评论

9

有一个MSDN示例是针对您的情况,使用本地代码。

注册设备通知

最好使用这种方式而不是通过WMI。


谢谢。我已经添加了一些额外的信息。如果您有任何其他想法,将会很有帮助。 - Jim Fell
@Jim Fell - 你发布的代码从未调用 OnDeviceChange。你是不是想在 WndProc 中看到 WM_DEVICECHANGE 时调用它?请注意,示例代码并没有无条件地调用 DefWindowProc,而是在 default: 情况下这样做。 - Steve Townsend
@Steve Townsend:你是说 DefWindowProc 的调用应该来自 switch 语句的 default 分支吗?我会像你建议的那样添加 OnDeviceChange,然后看看情况如何。谢谢。 - Jim Fell
@Steve Townsend:我明白了,谢谢。我认为针对这个应用程序,我不一定想要覆盖DefWindowProc;我想要处理某些消息以便于我的应用程序。我确实实现了你关于OnDeviceChange的建议,很有道理。 - Jim Fell
继续加油,吉姆。这是复杂的逻辑,但我相信你正在正确的轨道上。 - Steve Townsend
显示剩余2条评论

7
我遵循了你提出的“新方法”,但是发现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 );
    } 
}

如果您在使用此代码时遇到奇怪的链接器错误,请参见 https://stackoverflow.com/q/69004409/4284627。 - Donald Duck

1
这是检测USB存储设备插入和拔出的另一种方法。
这段C++代码可以检测USB存储设备的插入和拔出,还可以同时检测多个设备的插入和拔出。
C++代码:在VISUAL STUDIO 2015中测试过。
您也可以检查其他类型的设备以进行插入和拔出。只需在函数“getUSBStorageDeviceList()”中的代码的if else中将传递的字符数组填充到其他类型的设备即可。
    #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;
    }

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接