深入解析C++程序中激发事件和COM中的事件处理
本机C++中的事件处理
在处理本机C++事件时,您分别使用event_source和event_receiver特性设置事件源和事件接收器,并指定type=native。这些特性允许应用它们的类在本机的非COM上下文中激发和处理事件。
声明事件
在事件源类中,对一个方法声明使用__event关键字可将该方法声明为事件。请确保声明该方法,但不要定义它;这样做会产生编译器错误,因为将该方法转换为事件时编译器会隐式定义它。本机事件可以是带有零个或多个参数的方法。返回类型可以是void或任何整型。
定义事件处理程序
在事件接收器类中,可定义事件处理程序,这些处理程序是具有与它们将处理的事件匹配的签名(返回类型、调用约定和参数)的方法。
将事件处理程序挂钩到事件
同样在事件接收器类中,可使用内部函数__hook将事件与事件处理程序关联,并可使用__unhook取消事件与事件处理程序的关联。您可将多个事件挂钩到一个事件处理程序,或将多个事件处理程序挂钩到一个事件。
激发事件
若要激发事件,只需调用声明为事件源类中的事件的方法即可。如果处理程序已挂钩到事件,则将调用处理程序。
本机C++事件代码
以下示例演示如何在本机C++中激发事件。若要编译并运行此示例,请参考代码中的注释。
示例代码
//evh_native.cpp #include<stdio.h> [event_source(native)] classCSource{ public: __eventvoidMyEvent(intnValue); }; [event_receiver(native)] classCReceiver{ public: voidMyHandler1(intnValue){ printf_s("MyHandler1wascalledwithvalue%d.\n",nValue); } voidMyHandler2(intnValue){ printf_s("MyHandler2wascalledwithvalue%d.\n",nValue); } voidhookEvent(CSource*pSource){ __hook(&CSource::MyEvent,pSource,&CReceiver::MyHandler1); __hook(&CSource::MyEvent,pSource,&CReceiver::MyHandler2); } voidunhookEvent(CSource*pSource){ __unhook(&CSource::MyEvent,pSource,&CReceiver::MyHandler1); __unhook(&CSource::MyEvent,pSource,&CReceiver::MyHandler2); } }; intmain(){ CSourcesource; CReceiverreceiver; receiver.hookEvent(&source); __raisesource.MyEvent(123); receiver.unhookEvent(&source); }
输出:
MyHandler2wascalledwithvalue123. MyHandler1wascalledwithvalue123.
COM中的事件处理
在COM事件处理中,您使用event_source和event_receiver特性分别设置事件源和事件接收器,并指定type=com。这些特性为自定义接口、调度接口和双重接口注入相应的代码,从而使这些接口能够应用到的类激发事件并通过COM连接点处理事件。
声明事件
在事件源类中,在接口声明上使用__event关键字以将该接口的方法声明为事件。当您将该接口的事件作为接口方法调用时,将激发这些事件。事件接口上的方法可以有零个或多个参数(应全是in参数)。返回类型可以是void或任何整型。
定义事件处理程序
在事件接收器类中,可定义事件处理程序,这些处理程序是具有与它们将处理的事件匹配的签名(返回类型、调用约定和参数)的方法。对于COM事件,调用约定不必匹配;有关详细信息,请参阅下文中的依赖于布局的COM事件。
将事件处理程序挂钩到事件
同样在事件接收器类中,可使用内部函数__hook将事件与事件处理程序关联,并可使用__unhook取消事件与事件处理程序的关联。您可将多个事件挂钩到一个事件处理程序,或将多个事件处理程序挂钩到一个事件。
注意
通常,有两种方法使COM事件接收器能够访问事件源接口定义。第一种是共享公共头文件,如下所示。第二种是将#import与embedded_idl导入限定符结合使用,以便让事件源类型库写入到保留了特性生成的代码的.tlh文件。
激发事件
若要激发事件,只需调用在事件源类中使用__event关键字声明的接口中的方法。如果处理程序已挂钩到事件,则将调用处理程序。
COM事件代码
下面的示例演示如何在COM类中激发事件。若要编译并运行此示例,请参考代码中的注释。
//evh_server.h #pragmaonce [dual,uuid("00000000-0000-0000-0000-000000000001")] __interfaceIEvents{ [id(1)]HRESULTMyEvent([in]intvalue); }; [dual,uuid("00000000-0000-0000-0000-000000000002")] __interfaceIEventSource{ [id(1)]HRESULTFireEvent(); }; classDECLSPEC_UUID("530DF3AD-6936-3214-A83B-27B63C7997C4")CSource;
接着是服务器:
//evh_server.cpp //compilewith:/LD //post-buildcommand:Regsvr32.exe/sevh_server.dll #define_ATL_ATTRIBUTES1 #include<atlbase.h> #include<atlcom.h> #include"evh_server.h" [module(dll,name="EventSource",uuid="6E46B59E-89C3-4c15-A6D8-B8A1CEC98830")]; [coclass,event_source(com),uuid("530DF3AD-6936-3214-A83B-27B63C7997C4")] classCSource:publicIEventSource{ public: __event__interfaceIEvents; HRESULTFireEvent(){ __raiseMyEvent(123); returnS_OK; } };
再然后是客户端:
//evh_client.cpp //compilewith:/link/OPT:NOREF #define_ATL_ATTRIBUTES1 #include<atlbase.h> #include<atlcom.h> #include<stdio.h> #include"evh_server.h" [module(name="EventReceiver")]; [event_receiver(com)] classCReceiver{ public: HRESULTMyHandler1(intnValue){ printf_s("MyHandler1wascalledwithvalue%d.\n",nValue); returnS_OK; } HRESULTMyHandler2(intnValue){ printf_s("MyHandler2wascalledwithvalue%d.\n",nValue); returnS_OK; } voidHookEvent(IEventSource*pSource){ __hook(&IEvents::MyEvent,pSource,&CReceiver::MyHandler1); __hook(&IEvents::MyEvent,pSource,&CReceiver::MyHandler2); } voidUnhookEvent(IEventSource*pSource){ __unhook(&IEvents::MyEvent,pSource,&CReceiver::MyHandler1); __unhook(&IEvents::MyEvent,pSource,&CReceiver::MyHandler2); } }; intmain(){ //CreateCOMobject CoInitialize(NULL); { IEventSource*pSource=0; HRESULThr=CoCreateInstance(__uuidof(CSource),NULL,CLSCTX_ALL,__uuidof(IEventSource),(void**)&pSource); if(FAILED(hr)){ return-1; } //Createreceiverandfireevent CReceiverreceiver; receiver.HookEvent(pSource); pSource->FireEvent(); receiver.UnhookEvent(pSource); } CoUninitialize(); return0; }
输出
MyHandler1wascalledwithvalue123. MyHandler2wascalledwithvalue123.
依赖于布局的COM事件
布局依赖性只是COM编程中的一个问题。在本机和托管事件处理中,处理程序的签名(返回类型、调用约定和参数)必须与其事件匹配,但处理程序的名称不必与其事件匹配。
但是,在COM事件处理中,如果将event_receiver的layout_dependent参数设置为true,则将强制名称和签名匹配。这意味着事件接收器中处理程序的名称和签名必须与处理程序将挂钩到的事件的名称和签名完全匹配。
当layout_dependent设置为false时,激发事件方法与挂钩方法(其委托)之间的调用约定和存储类(虚拟、静态等)可以混合和匹配。将layout_dependent设置为true效率会稍微高一点。
例如,假设IEventSource定义为具有下列方法:
[id(1)]HRESULTMyEvent1([in]intvalue); [id(2)]HRESULTMyEvent2([in]intvalue);
假定事件源具有以下形式:
[coclass,event_source(com)] classCSource:publicIEventSource{ public: __event__interfaceIEvents; HRESULTFireEvent(){ MyEvent1(123); MyEvent2(123); returnS_OK; } };
则在事件接收器中,挂钩到IEventSource中的方法的任何处理程序必须与其名称和签名匹配,如下所示:
[coclass,event_receiver(com,true)] classCReceiver{ public: HRESULTMyEvent1(intnValue){//nameandsignaturematchesMyEvent1 ... } HRESULTMyEvent2(Ec,char*pc){//signaturedoesn'tmatchMyEvent2 ... } HRESULTMyHandler1(intnValue){//namedoesn'tmatchMyEvent1(or2) ... } voidHookEvent(IEventSource*pSource){ __hook(IFace,pSource);//Hooksupallname-matchedevents //underlayout_dependent=true __hook(&IFace::MyEvent1,pSource,&CReceive::MyEvent1);//valid __hook(&IFace::MyEvent2,pSource,&CSink::MyEvent2);//notvalid __hook(&IFace::MyEvent1,pSource,&CSink::MyHandler1);//notvalid } };