非常感谢原作者ysjyniiq的博客,原文博客地址如下:
http://blog.csdn.net/ysjyniiq/article/details/6428809
ATL所自带响应Event的类有两个
IDispEventSimpleImpl
IDispEventImpl
它们的区别是一个是否带类型库,现在看看他们的模板参数
template <UINT nID, class T, const IID* pdiid>
class ATL_NO_VTABLE IDispEventSimpleImpl : public _IDispEventLocator<nID, pdiid>
{
};
template <UINT nID, class T, const IID* pdiid = &IID_NULL, const GUID* plibid = &GUID_NULL, WORD wMajor = 0, WORDwMinor = 0, class tihclass = CComTypeInfoHolder>
class ATL_NO_VTABLE IDispEventImpl : public IDispEventSimpleImpl<nID, T, pdiid>
{
};
可以看到IDispEventImpl的参数太多,居然连pdiid都可以默认参数。
区别:
IDispEventImpl能从IUnknow里QueryInterface出它的类型库,得到Event函数的详细结构,使下面SINK结构构造简单。而IDispEventSimpleImpl不使用类型库,使用手工打造,SINK结构自然要麻烦一些。
参数说明:
nID:控件ID,如果所有父类不是对话框类,这个值,可以随便设一个。但必须跟下面的SINK结构的ID相一致。
T:父类
pdiid:EVENT的IID。如果SINK结构是使用SINK_ENTRY_EX或SINK_ENTRY_INFO,里面的iid必须跟这里一致。
plibid:LIBID。如果pdiid用默认值IID_NULL,这个值最好用默认值。原因下面再解析。
wMajor和wMinor:版本信息。其用法跟plibid一样。
使用:
连接:
可供选择两个函数。
HRESULT DispEventAdvise(IUnknown* pUnk, const IID* piid)
HRESULT Advise(IUnknown *punk)
DispEventAdvise:
适用于不是默认连接的情况(只要COM对象提供有EVENT就可以连接),这种情况由指定的piid进行连接。
但前提IDispEventImpl或IDispEventSimpleImpl必须有指定参数,不能采用默认参数,否则很可能会工作不正常。
Advise:
仅适用于默认连接的情况,这个函数用起来很舒服。它里面使调用AtlGetObjectSourceInterface获得默认类型信息,并存起来。仅适用于IDispEventImpl。
如果IDispEventSimpleImpl会因为在invoke时候发现有类型信息,但又不能处理类型信息而产生错误
断开:
HRESULT Unadvise(IUnknown *punk);
HRESULT DispEventUnadvise(IUnknown* pUnk, const IID* piid);
最好跟连接的相配套,但Unadvise最终会调用DispEventUnadvise,所以也可以直接DispEventUnadvise。
IDispEventImpl如果使用默认值的情况下,也就是使用Advise的情况下,其模板的构造版本,使不使用默认值,不重要,因为它会使用AtlGetObjectSourceInterface获得IID,LIBID,及版本信息。
但如果使用DispEventAdvise,IDispEventImpl的构造就相对麻烦很多,全部参数,都需要老老实实地填 ,而且不能错。IID,LIBID一般都能从头文件里得到,但版本呢?可以使用AtlGetObjectSourceInterface执行一次,查看一下,得到具体值,再构造这个模板。
SINK结构:
下面再看一下SINK结构吧。
BEGIN_SINK_MAP(className)
SINK_ENTRY(IDC_A171, 1, TextA171) SINK_ENTRY_EX(2,__uuidof(HTMLDocumentEvents),DISPID_HTMLDOCUMENTEVENTS_ONMOUSEDOWN,func)
SINK_ENTRY_INFO(1,__uuidof(_Itt3Events) , 1, OnMyFunc, &StatusChangeInfo)
END_SINK_MAP()
SINK结构与传统的MAP结构类似,也是begin-end结构,上面的结构是所有IDispEventSimpleImpl和IDispEventImpl共用。
从上面代码可以看到最简单是SINK_ENTRY,接着是SINK_ENTRY_EX, SINK_ENTRY_INFO,现在再来看看这三个结构的关系
#define SINK_ENTRY_INFO(id, iid, dispid, fn, info) {id, &iid, (int)(INT_PTR)(static_cast<ATL::_IDispEventLocator<id, &iid>*>((_atl_event_classtype*)8))-8, dispid, (void (__stdcall _atl_event_classtype::*)())fn, info},
#define SINK_ENTRY_EX(id, iid, dispid, fn) SINK_ENTRY_INFO(id, iid, dispid, fn, NULL)
#define SINK_ENTRY(id, dispid, fn) SINK_ENTRY_EX(id, IID_NULL, dispid, fn)
SINK_ENTRY会从SINK_ENTRY_EX逐步演变为SINK_ENTRY_INFO,复杂的代码不说,光说应用吧。
参数说明:
id:控件ID,与前面的Event类的ID相一致
dispid:EVENT里函数或属性的Dispatch ID
iid:EVENT的IID
fn:函数名字
info:函数参数结构,是最复杂的一项。(这里暂不解析,由于麻烦,一般不采用这种方式)
SINK_ENTRY:
所以在EVENT模板参数的pdiid必须为IID_NULL,否则只能使用SINK_ENTRY_EX或SINK_ENTRY_INFO,但如果在IDispEventSimpleImpl使用IID_NULL作为IDD参数,尽管可以使用SINK_ENTRY通过编译,但最终运行的时候,没有类型信息的函数,所以最后还是运行错误。简单的话说,这个宏只能用于IDispEventImpl,而且pdiid==IID_NULL的情况。
SINK_ENTRY_EX:
也只能用于IDispEventImpl,需要IID参数
SINK_ENTRY_INFO:
是最复杂的情况,可以用于IDispEventImpl和IDispEventSimpleImpl,但要要构造麻烦info参数,这不一是一件爽事。