深入解析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)]
class CSource {
public:
  __event void MyEvent(int nValue);
};

[event_receiver(native)]
class CReceiver {
public:
  void MyHandler1(int nValue) {
   printf_s("MyHandler1 was called with value %d.\n", nValue);
  }

  void MyHandler2(int nValue) {
   printf_s("MyHandler2 was called with value %d.\n", nValue);
  }

  void hookEvent(CSource* pSource) {
   __hook(&CSource::MyEvent, pSource, &CReceiver::MyHandler1);
   __hook(&CSource::MyEvent, pSource, &CReceiver::MyHandler2);
  }

  void unhookEvent(CSource* pSource) {
   __unhook(&CSource::MyEvent, pSource, &CReceiver::MyHandler1);
   __unhook(&CSource::MyEvent, pSource, &CReceiver::MyHandler2);
  }
};

int main() {
  CSource source;
  CReceiver receiver;

  receiver.hookEvent(&source);
  __raise source.MyEvent(123);
  receiver.unhookEvent(&source);
}

输出:

MyHandler2 was called with value 123.
MyHandler1 was called with value 123.

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
#pragma once

[ dual, uuid("00000000-0000-0000-0000-000000000001") ]
__interface IEvents {
  [id(1)] HRESULT MyEvent([in] int value);
};

[ dual, uuid("00000000-0000-0000-0000-000000000002") ]
__interface IEventSource {
  [id(1)] HRESULT FireEvent();
};

class DECLSPEC_UUID("530DF3AD-6936-3214-A83B-27B63C7997C4") CSource;

接着是服务器:

// evh_server.cpp
// compile with: /LD
// post-build command: Regsvr32.exe /s evh_server.dll
#define _ATL_ATTRIBUTES 1
#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")]
class CSource : public IEventSource {
public:
  __event __interface IEvents; 

  HRESULT FireEvent() {
   __raise MyEvent(123);
   return S_OK;
  }
};

再然后是客户端:

// evh_client.cpp
// compile with: /link /OPT:NOREF
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include <stdio.h>
#include "evh_server.h"

[ module(name="EventReceiver") ];

[ event_receiver(com) ]
class CReceiver {
public:
  HRESULT MyHandler1(int nValue) {
   printf_s("MyHandler1 was called with value %d.\n", nValue);
   return S_OK;
  }

  HRESULT MyHandler2(int nValue) {
   printf_s("MyHandler2 was called with value %d.\n", nValue);
   return S_OK;
  }

  void HookEvent(IEventSource* pSource) {
   __hook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler1);
   __hook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler2);
  }

  void UnhookEvent(IEventSource* pSource) {
   __unhook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler1);
   __unhook(&IEvents::MyEvent, pSource, &CReceiver::MyHandler2);
  }
};

int main() {
  // Create COM object
  CoInitialize(NULL);
  {
   IEventSource* pSource = 0;
   HRESULT hr = CoCreateInstance(__uuidof(CSource), NULL,     CLSCTX_ALL, __uuidof(IEventSource), (void **) &pSource);
   if (FAILED(hr)) {
     return -1;
   }

   // Create receiver and fire event
   CReceiver receiver;
   receiver.HookEvent(pSource);
   pSource->FireEvent();
   receiver.UnhookEvent(pSource);
  }
  CoUninitialize();
  return 0;
}

输出

MyHandler1 was called with value 123.
MyHandler2 was called with value 123.

依赖于布局的 COM 事件
布局依赖性只是 COM 编程中的一个问题。在本机和托管事件处理中,处理程序的签名(返回类型、调用约定和参数)必须与其事件匹配,但处理程序的名称不必与其事件匹配。
但是,在 COM 事件处理中,如果将 event_receiver 的 layout_dependent 参数设置为 true,则将强制名称和签名匹配。这意味着事件接收器中处理程序的名称和签名必须与处理程序将挂钩到的事件的名称和签名完全匹配。
当 layout_dependent 设置为 false 时,激发事件方法与挂钩方法(其委托)之间的调用约定和存储类(虚拟、静态等)可以混合和匹配。将 layout_dependent 设置为 true 效率会稍微高一点。
例如,假设 IEventSource 定义为具有下列方法:

[id(1)] HRESULT MyEvent1([in] int value);
[id(2)] HRESULT MyEvent2([in] int value);

假定事件源具有以下形式:

[coclass, event_source(com)]
class CSource : public IEventSource {
public:
  __event __interface IEvents;

  HRESULT FireEvent() {
   MyEvent1(123);
   MyEvent2(123);
   return S_OK;
  }
};

则在事件接收器中,挂钩到 IEventSource 中的方法的任何处理程序必须与其名称和签名匹配,如下所示:

[coclass, event_receiver(com, true)]
class CReceiver {
public:
  HRESULT MyEvent1(int nValue) { // name and signature matches MyEvent1
   ...
  }
  HRESULT MyEvent2(E c, char* pc) { // signature doesn't match MyEvent2
   ...
  }
  HRESULT MyHandler1(int nValue) { // name doesn't match MyEvent1 (or 2)
   ...
  }
  void HookEvent(IEventSource* pSource) {
   __hook(IFace, pSource); // Hooks up all name-matched events
                // under layout_dependent = true
   __hook(&IFace::MyEvent1, pSource, &CReceive::MyEvent1);  // valid
   __hook(&IFace::MyEvent2, pSource, &CSink::MyEvent2);  // not valid
   __hook(&IFace::MyEvent1, pSource, &CSink:: MyHandler1); // not valid
  }
};
(0)

相关推荐

  • C++访问注册表获取已安装软件信息列表示例代码

    复制代码 代码如下: // ---------------------------------------------------------------// FlieNmae: //   SofInfo.h// Remark://   通过读取注册表获得本机已安装软件信息.// ---------------------------------------------------------------#pragma once#include <vector> struct SoftInfo

  • 解决C++中事件不响应的方法详解

    在C++开发过程由于这样那样的原因,可以会出现点击菜单.右键菜单无响应的问题,或者点击A菜单,但是响应的却是B菜单.遇到上述问题时,你可以从下面几个方面分析,一般都可以解决问题.下面从四个方面来解决问题: 1.检查菜单ID对于的事件映射表是否存在,如果不存在肯定不会响应2.检查菜单ID与其它菜单ID是否出现重复,如果出现重复可能出现张冠李戴的现象(点击A菜单,但是响应B菜单的行为)3.菜单ID与对应菜单事件的映射表ID是否一致,可能出现菜单ID与事件映射表的ID不一致4.在Qt中,要注意信号函数

  • C++事件处理中__event与__raise关键字的用法讲解

    __event 声明事件. 语法 __event method-declarator ; __event __interface interface-specifier; __event member-declarator; 备注 关键字 __event 可应用于方法声明.接口声明或数据成员声明.但是,不能使用 __event 关键字限定嵌套类的成员. 根据您的事件源和接收器是本机 C++.COM 还是托管 (.NET Framework),您可使用下列构造作为事件: 在事件接收器中使用 __h

  • C++中事件机制的简洁实现及需要放弃的特性

    事件模型是被广泛使用的好东西,但是C++标准库里没有现成的,其他实现又复杂或者不优雅,比如需要使用宏.现在VC11可以用在XP下了,那么就痛快的拿起C++11提供的先进设施组合出一个轻便的实现吧. 为了达到简洁的目的,需要放弃一些特性: 1.不支持判断函数是否已经绑定过(因为std::function不提供比较方法,自己实现function的话代码又变多了) 2.需要使用者接收返回的回调函数标识来移除事件绑定(原因同上) 3.事件没有返回值,不支持回调函数优先级.条件回调等事件高级特性(比如返回

  • VC++实现添加文件关联的方法示例

    本文实例讲述了VC++实现添加文件关联的方法.分享给大家供大家参考,具体如下: // 检测文件关联情况 // strExt: 要检测的扩展名(例如: ".txt") // strAppKey: ExeName扩展名在注册表中的键值(例如: "txtfile") // 返回TRUE: 表示已关联,FALSE: 表示未关联 BOOL CheckFileRelation(const char *strExt, const char *strAppKey) { int nR

  • C++事件处理中的__hook与__unhook用法详解

    __hook __hook将处理程序方法与事件关联. 语法 long __hook( &SourceClass::EventMethod, source, &ReceiverClass::HandlerMethod [, receiver = this] ); long __hook( interface, source ); 参数 &SourceClass::EventMethod 指向要将事件处理程序方法挂钩到的事件方法的指针: 本机 C++ 事件:SourceClass 是事

  • C++事件驱动型银行排队模拟

    最近重拾之前半途而废的C++,恰好看到了<C++ 实现银行排队服务模拟>,但是没有实验楼的会员,看不到具体的实现,正好用来作为练习. 模拟的是银行的排队叫号系统,所有顾客以先来后到的顺序在同一个队列中等待,当有服务窗口空闲时,则队首的顾客接受服务,完成后则下一位顾客开始接受服务. 本实现是事件驱动型的,处理对象是事件而不是顾客:  有2种事件:顾客到事件和顾客离开事件.  有2个队列:顾客队列和事件队列. 程序的逻辑如下:  1.初始化事件队列,填充顾客到达事件:  2.处理事件队列的头部(总

  • VC++实现文件与应用程序关联的方法(注册表修改)

    本文实例讲述了VC++实现文件与应用程序关联的方法.分享给大家供大家参考,具体如下: 日常工作中,doc文件直接双击后,就能启动word软件,并读取该文档的内容在软件中显示,这都得益于注册表的配置,我们的软件也需要实现这样的功能,该如何写注册表以及写入哪些内容呢?下面的两个函数就能实现这个功能.CheckFileRelation是检查注册表中是否已经将我们期待的文件格式与相应软件关联了:RegisterFileRelation是直接往注册表中写入相关的key和value. /**********

  • C++设置事件通知线程工作的方法

    本文实例讲述了C++设置事件通知线程工作的方法,其中主线程通过将事件状态设置为"受信"来通知工作线程工作.具体实现方法如下: 复制代码 代码如下: // eventDemo.cpp : 定义控制台应用程序的入口点.  //    #include "stdafx.h"  #include <Windows.h>  #include <process.h>  HANDLE g_event;    UINT __stdcall ThreadPro

  • C++写注册表项实例

    本文实例讲述了C++写注册表实现开机启动的方法.分享给大家供大家参考. 具体实现方法如下: 复制代码 代码如下: void SelfRun(LPSTR lpszValueName) //lpszValueName 显示的名称  {      LPCTSTR lpSubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Run";      HKEY hKey;      DWORD dwDisposition = REG_OPENE

随机推荐