USB及手机平板设备插拔响应解决方案
(一)、基本原理:WM_DEVICECHANGE消息响应
一般WM_DEVICECHANGE只发给顶层窗口。你可以自己创建一个隐藏的顶层窗口来接收这个消息。
(二)、在VC6中,一般要手动添加这个消息的映射代码,分三步,过程如下:
第1步:在窗口类的.h文件中增加:.
afx_msgBOOLOnDeviceChange(UINTnEventType,DWORDdwData);
第2步:在窗口类的.cpp文件中增加:
DECLARE_MESSAGE_MAP()
ON_WM_DEVICECHANGE()
END_MESSAGE_MAP()
第3步:在窗口类的.cpp文件中增加:
BOOLCClassCtrl::OnDeviceChange(UINTnEventType,DWORDdwData)
{
return(TRUE);//返回TRUE表示不拒绝此操作
}
(三)在OnDeviceChange的参数中的nEventType,为如下状态定义:
#defineDBT_DEVICEARRIVAL0x8000//systemdetectedanewdevice
#defineDBT_DEVICEQUERYREMOVE0x8001//wantstoremove,mayfail
#defineDBT_DEVICEQUERYREMOVEFAILED0x8002//removalaborted
#defineDBT_DEVICEREMOVEPENDING0x8003//abouttoremove,stillavail.
#defineDBT_DEVICEREMOVECOMPLETE0x8004//deviceisgone
#defineDBT_DEVICETYPESPECIFIC0x8005//typespecificevent
#if(WINVER>=0x040A)
#defineDBT_CUSTOMEVENT0x8006//user-definedevent
#endif/*WINVER>=0x040A*/
(四)在OnDeviceChange的参数中的dwData,一般为如下状态定义:
PDEV_BROADCAST_HDRpHdr=(PDEV_BROADCAST_HDR)dwData;
if(pHdr->dbch_devicetype==DBT_DEVTYP_DEVICEINTERFACE)
{
PDEV_BROADCAST_DEVICEINTERFACEpInf=(PDEV_BROADCAST_DEVICEINTERFACE)pHdr;//pInf指向设备相关的信息结构体,参见以下节中的定义
}
(五)由WM_DEVICECHANGE获取设备GUID及设备名(可能含有VID-PID及SN)
typedefstruct_DEV_BROADCAST_DEVICEINTERFACE_A{
DWORDdbcc_size;
DWORDdbcc_devicetype;
DWORDdbcc_reserved;
GUIDdbcc_classguid;
chardbcc_name[1];
}DEV_BROADCAST_DEVICEINTERFACE_A,*PDEV_BROADCAST_DEVICEINTERFACE_A;
dbcc_classguid类似于:{A5DCBF10-6530-11D2-901F-00C04FB951ED}
(六)下面是一些常用的设备的class的GUID举例:
USBRawDevice/USB设备
{a5dcbf10-6530-11d2-901f-00c04fb951ed}
DiskDevice/磁盘设备
{53f56307-b6bf-11d0-94f2-00a0c91efb8b}
NetworkCard/网卡
{ad498944-762f-11d0-8dcb-00c04fc3358c}
HumanInterfaceDevice(HID)
人机界面设备
{4d1e55b2-f16f-11cf-88cb-001111000030}
Palm/手持设备
{784126bf-4190-11d4-b5c2-00c04f687a67}
(七)如何“注册”设备的classGUID
对应的设备事件通知(WINVER0x0500方法):
#include<Dbt.h>
DEV_BROADCAST_DEVICEINTERFACEdbi;
ZeroMemory(&dbi,sizeof(dbi));
dbi.dbcc_size=sizeof(dbi);
dbi.dbcc_devicetype=DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_reserved=0;
dbi.dbcc_classguid=hid_guid;
HDEVNOTIFYhDevNotify;
hDevNotify=RegisterDeviceNotification(m_hWnd,
&dbi,DEVICE_NOTIFY_WINDOW_HANDLE);
if(!hDevNotify)
{
intErr=GetLastError();
printf("RegisterDeviceNotificationfailed:%lx.\n",Err);
return(FALSE);
}
注意:在程序结束前,一定要记得反注册:
UnregisterDeviceNotification(hDevNotify);
因默认VC6的WINVER宏定义是<0x0500的,故在必要时,在StdAfx.h中添加:
#if_MSC_VER>1000
#pragmaonce
#endif//_MSC_VER>1000
#defineWINVER0x0500
#defineVC_EXTRALEAN//Excluderarely-usedstufffromWindowsheaders
(八)如何“注册”设备的classGUID对应的设备事件通知(动态加载-方法):
//注册设备通知事件,须与反注册成对儿使用
PVOIDRegistCapvNotice(HWNDhWnd)
{
if(!hWnd||!::IsWindow(hWnd))return(NULL);
HMODULEhModUser=GetModuleHandle(TEXT("user32.dll"));
if(!hModUser)return(NULL);
PRegistDevNotifyfnReg=(PRegistDevNotify)GetProcAddress(\
hModUser,TEXT("RegisterDeviceNotificationA"));
if(!fnReg)return(NULL);
STDEVINTRFdbci;
ZeroMemory(&dbci,sizeof(STDEVINTRF));
dbci.dbcc_size=sizeof(STDEVINTRF);
dbci.dbcc_devicetype=0x00000005;
dbci.dbcc_classguid=AM_KSCATEGORY_CAPTURE;
return(fnReg(hWnd,&dbci,0x00000000));
}
//取消注册设备通知(反注册),须与注册成对儿使用
BOOLUnRegistCapvNotice(PVOIDhHandle)
{
if(!hHandle)return(TRUE);
HMODULEhModUser=GetModuleHandle(TEXT("user32.dll"));
if(!hModUser)return(FALSE);
PUnRegistDevNotifyfnUreg=(PUnRegistDevNotify)GetProcAddress(\
hModUser,TEXT("UnregisterDeviceNotification"));
if(!fnUreg)return(FALSE);
return(fnUreg(hHandle));
}
注意:上述在注册的classguid为AM_KSCATEGORY_CAPTURE,视频捕捉设备,可按需更换。
九)在DirectShow里,可以响应EC_DEVICE_LOST事件,来响应视频设备的插拔,它有2参数:lParam1为(IUnknown*)Pointer,是描述当前设备的过滤器的。lParam2为0=设备已经移除,1=设备又重新有效了(又上了)由于在移除后,设备过滤器不再有效,所以必须重建graph才行。实现的过滤分以下几步:
1、在类的.h里,增加IMediaEventEx*m_pEvent;//过滤器事件通报
2、然后在创建IGraphBuilder后,获取m_pEvent接口:
hr=m_pGB->QueryInterface(IID_IMediaEventEx,(void**)&m_pEvent);
3、在设置视频窗口时,把事件的目标窗口设置为所需要的:
hr=m_pEvent->SetNotifyWindow((OAHWND)m_hVdad,WM_DXGRAPHNOTIFY,0);
其中WM_DXGRAPHNOTIFY为窗口自定义消息,可以定义如下:
#defineWM_DXGRAPHNOTIFY(WM_APP+110)
4、然后在m_hVdad所在目标窗口类中,增加自定义消息WM_DXGRAPHNOTIFY的响应代码:
在.h中增加:
afx_msgLRESULTOnDshowNotifyForMain(WPARAMwParam,LPARAMlParam);
DECLARE_MESSAGE_MAP()
在.cpp中增加:
ON_MESSAGE(WM_DXGRAPHNOTIFY,OnDshowNotifyForMain)
END_MESSAGE_MAP()
LRESULTCClassCtrl::OnDshowNotifyForMain(WPARAMwParam,LPARAMlParam)
{
DoEvt();return(0);//增加自定义处理,DoEvt参见下面定义
}
5、DoEvt()里面需要使用m_pEvent接口,故m_pEvent可为全局量,为类中量DoEvt外
用:
voidCClass::DoEvt(void)
{
longevCode,param1,param2;
while(m_pEvent&&SUCCEEDED(m_pEvent->GetEvent(\
&evCode,¶m1,¶m2,0)))//
遍历全部当前事件
{
m_pEvent->FreeEventParams(evCode,param1,param2);
if(evCode==EC_DEVICE_LOST&&!param2)Close();
}
}
(十)为了有效地判断视频的插拔,可以采用如下2种方法。
第1种为:只使用WM_DEVICECHANGE消息,注册AM_KSCATEGORY_CAPTURE视频设备通知。这样可以获取到DEV_BROADCAST_DEVICEINTERFACE结构体,里面有dbcc_name设备名。
可以创建一个关于此dbcc_name设备的管理列表,响应插拔动作,与dbcc_name检查。
第2种为:对插和拔两个动作,分而治之:
新到:使用WM_DEVICECHANGE消息来,获取设备插上的消息,打开新到设备。
移除:再使用EC_DEVICE_LOST消息,来响应对应某个设备的移除。
这是由于,EC_DEVICE_LOST在没有建立有效的Graph时,新到的设备,它是无法感知的。EC_DEVICE_LOST只能是graph建立后,感知设备的移除,和它再次插入。所以,EC_DEVICE_LOST感知移除是很有效和针对性的,再配合WM_DEVICECHANGE来判断新来插入的设备,再创建新的实例。