我正在寻找通过Windows API检测MTP设备,而不仅仅是枚举已连接的设备。
首先,请考虑查看:
- 视窗便携式设备
- Windows 便携式设备开发工具包和下载
- 便携式设备 COM API 示例
这将是DBT_DEVICEARRIVAL
事件。
详细阐述 MSalters 的答案,这个问题的答案可能会对您有所帮助 - C++ Win32 未收到DBT_DEVICEARRIVAL或DBT_DEVICEREMOVECOMPLETE WM_DEVICECHANGE
基本总结如下:
- 在处理
WM_CREATE
消息时,调用RegisterDeviceNotification
,传递要筛选的 USB 类 GUID 作为第二个参数,或DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
作为第三个参数以处理所有 USB 设备类 - 在处理
WM_DEVICECHANGE
消息时,将消息的lParam
字段转换为PDEV_BROADCAST_HDR
结构,如果其dbch_devicetype
字段指示它是DBT_DEVTYP_DEVICEINTERFACE
,则进一步将其转换为PDEV_BROADCAST_DEVICEINTERFACE
;同时使用wParam
字段确定消息是否与DBT_DEVICEARRIVAL
、DBT_DEVICEREMOVECOMPLETE
或DBT_DEVNODES_CHANGED
事件相关联(在最后一种情况下将NULL
lParam
) - 在处理
WM_CLOSE
消息时,使用步骤 1 返回的句柄调用UnregisterDeviceNotification
。
除了上面链接的SO问题外,我还参考了检测媒体插入或移除和注册设备通知。 我相信这个问题也参考了它们。
下面是我的主要来源的完整列表,最初基于股票 Windows 桌面向导项目模板,应用程序类型桌面应用程序 (.exe) 选择了预编译标头。 不是我最干净的代码,有点像上述参考文献的弗兰肯斯坦,但它明白了这一点。
// DevDetectDemo.cpp : Defines the entry point for the application.
//
#include "pch.h"
#include "framework.h"
#include "DevDetectDemo.h"
#define MAX_LOADSTRING 100
// Global Variables:
HINSTANCE hInst; // current instance
WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// Forward declarations of functions included in this code module:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
BOOL DoRegisterDeviceInterfaceToHwnd(GUID, HWND, HDEVNOTIFY*);
void Main_OnDeviceChange(HWND hwnd, WPARAM wParam, LPARAM lParam);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_DEVDETECTDEMO, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DEVDETECTDEMO));
MSG msg;
// Main message loop:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DEVDETECTDEMO));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_DEVDETECTDEMO);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static HDEVNOTIFY hDeviceNotify;
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(
GUID_DEVINTERFACE_USB_DEVICE,
hWnd,
&hDeviceNotify))
{
// Terminate on failure.
ExitProcess(1);
}
break;
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
EndPaint(hWnd, &ps);
}
break;
case WM_DEVICECHANGE:
Main_OnDeviceChange(hWnd, wParam, lParam);
break;
case WM_CLOSE:
UnregisterDeviceNotification(hDeviceNotify);
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
BOOL DoRegisterDeviceInterfaceToHwnd(
IN GUID InterfaceClassGuid,
IN HWND hWnd,
OUT HDEVNOTIFY* hDeviceNotify
)
// Routine Description:
// Registers an HWND for notification of changes in the device interfaces
// for the specified interface class GUID.
// Parameters:
// InterfaceClassGuid - The interface class GUID for the device
// interfaces.
// hWnd - Window handle to receive notifications.
// hDeviceNotify - Receives the device notification handle. On failure,
// this value is NULL.
// Return Value:
// If the function succeeds, the return value is TRUE.
// If the function fails, the return value is FALSE.
// Note:
// RegisterDeviceNotification also allows a service handle be used,
// so a similar wrapper function to this one supporting that scenario
// could be made from this template.
{
DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
NotificationFilter.dbcc_classguid = InterfaceClassGuid;
*hDeviceNotify = RegisterDeviceNotification(
hWnd, // events recipient
&NotificationFilter, // type of device
DEVICE_NOTIFY_WINDOW_HANDLE // type of recipient handle; can also be
// DEVICE_NOTIFY_ALL_INTERFACE_CLASSES to
// ignore filter and notify of all devices
);
if (NULL == *hDeviceNotify)
{
return FALSE;
}
return TRUE;
}
/*------------------------------------------------------------------
Main_OnDeviceChange( hwnd, wParam, lParam )
Description
Handles WM_DEVICECHANGE messages sent to the application's
top-level window.
--------------------------------------------------------------------*/
void Main_OnDeviceChange(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam;
TCHAR szMsg[256];
switch (wParam)
{
case DBT_DEVICEARRIVAL:
// Check whether a device was inserted.
if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
PDEV_BROADCAST_DEVICEINTERFACE lpdbd = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
GUID guid = lpdbd->dbcc_classguid;
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]),
TEXT("Device %s Media with class GUID {%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX} has arrived.n"),
lpdbd->dbcc_name,
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
MessageBox(hwnd, szMsg, TEXT("WM_DEVICECHANGE"), MB_OK);
}
break;
case DBT_DEVICEREMOVECOMPLETE:
// Check whether a device was removed.
if (lpdb->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
{
PDEV_BROADCAST_DEVICEINTERFACE lpdbd = (PDEV_BROADCAST_DEVICEINTERFACE)lpdb;
GUID guid = lpdbd->dbcc_classguid;
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]),
TEXT("Device %s Media with class GUID {%08lX-%04hX-%04hX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX} was removed.n"),
lpdbd->dbcc_name,
guid.Data1, guid.Data2, guid.Data3,
guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3],
guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
MessageBox(hwnd, szMsg, TEXT("WM_DEVICECHANGE"), MB_OK);
}
break;
case DBT_DEVNODES_CHANGED:
// Check whether a device has been added to or removed from the system.
StringCchPrintf(szMsg, sizeof(szMsg) / sizeof(szMsg[0]),
TEXT("Device was was added/removed.n"));
MessageBox(hwnd, szMsg, TEXT("WM_DEVICECHANGE"), MB_OK);
break;
default:
/*
Process other WM_DEVICECHANGE notifications for other
devices or reasons.
*/
;
}
}
随附的标题:
// pch.h: This is a precompiled header file.
// Files listed below are compiled only once, improving build performance for future builds.
// This also affects IntelliSense performance, including code completion and many code browsing features.
// However, files listed here are ALL re-compiled if any one of them is updated between builds.
// Do not add files here that you will be updating frequently as this negates the performance advantage.
#ifndef PCH_H
#define PCH_H
// add headers that you want to pre-compile here
#include "framework.h"
#include <Dbt.h>
#include <strsafe.h>
#include <initguid.h>
#include <Usbiodef.h>
#pragma comment(lib, "user32.lib")
#endif //PCH_H
框架.h
#pragma once
#include "targetver.h"
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
DevDetectDemo.h
#pragma once
#include "resource.h"
我发现对于我的设备,与DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
连接和断开连接会导致指示许多类 GUID,具体取决于是否在开发人员选项中启用了 USB 调试。 没有USB调试:
- {6AC27878-A6FA-4155-BA85-F98F491D4F33} - GUID_DEVINTERFACE_WPD
- {6BDD1FC6-810F-11D0-BEC7-08002BE2092F} - GUID_DEVINTERFACE_IMAGE
- {A5DCBF10-6530-11D2-901F-00C04FB951ED} - GUID_DEVINTERFACE_USB_DEVICE
- {F33FDC04-D1AC-4E8E-9A30-19BBD4B108AE} - PAP 设备接口类
使用USB调试:
- {A5DCBF10-6530-11D2-901F-00C04FB951ED} - GUID_DEVINTERFACE_USB_DEVICE
- {DEE824EF-729B-4A0E-9C14-B7117D33A817} - USB设备类
- {F72FE0D4-CBCB-407D-8814-9ED673D0DD6B} - ANDROID_USB_CLASS_ID
如您所见,两个集合之间唯一通用的 GUID 是 GUID_DEVINTERFACE_USB_DEVICE,这是我在上面的示例中使用的。