这次我们来看一下连接点的基本工作原理。画了一个简单的图:

从上面的图,大概也可以看到基本结构了。如果一个COM对象要支持连接点的话,那么这个对象类一定要从IConnectionPointImpl继承下来。通常,会有一个proxy类,上图中的CProxy_IMyCarEvents。这个proxy类一般会有这样的代码:

#pragma oncetemplate<class T>
class CProxy_IMyCarEvents :public ATL::IConnectionPointImpl<T, &__uuidof(_IMyCarEvents)>
{
public:HRESULT Fire_OnStop(FLOAT Distance){HRESULT hr = S_OK;T * pThis = static_cast<T *>(this);int cConnections = m_vec.GetSize();for (int iConnection = 0; iConnection < cConnections; iConnection++){pThis->Lock();CComPtr<IUnknown> punkConnection = m_vec.GetAt(iConnection);pThis->Unlock();IDispatch * pConnection = static_cast<IDispatch *>(punkConnection.p);if (pConnection){CComVariant avarParams[1];avarParams[0] = Distance;avarParams[0].vt = VT_R4;CComVariant varResult;DISPPARAMS params = { avarParams, NULL, 1, 0 };hr = pConnection->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, ¶ms, &varResult, NULL, NULL);}}return hr;}
};

其实这个类很简单,比如那里面有一个函数Fire_OnStop。这个函数内部就是遍历一下m_vec数组,这个数组存放的就是sink对象。然后遍历整个数组,把每个sink对象的Invoke函数调用一下。简单说,如果COM组件完成了一项任何,需要通知所有sink的话,就是调用一下Fire函数。这样fire函数会调用所有sink的Invoke。比如:

STDMETHODIMP CMyCar::Run()
{// TODO: Add your implementation code herethis->Fire_OnStop(1000);return S_OK;
}

上面这个例子代码,就是在Run()函数里面,调用fire,然后fire函数会调用这个对象里面的所有sink的Invoke。

_IMyCarEvents定义了所有支持的event,比如下面的例子就有一个OnStop事件。

library MyComLib
{importlib("stdole2.tlb");[uuid(2CF347A8-63ED-4CE0-8A6D-F98D60C98B8C)     ]dispinterface _IMyCarEvents{properties:methods:[id(1)] HRESULT OnStop([in] FLOAT Distance);};[uuid(DA6770F3-CBB6-4F34-A137-2B02A27AB219)       ]coclass MyCar{[default] interface IMyCar;[default, source] dispinterface _IMyCarEvents;};
};

注意:CMyCar有一个基类叫做IConnectionPointContainerImpl。一个IConnectionPointContainerImpl内部可以有多个连接点,也就是说,我们现在已经有了一个连接点:_IMyCarEvents。那么可以再增加一个连接点,比如叫做_IMyCarEvents2,然后这两个连接点都可以放到Container中。(回头再写个例子)

CSink是一个接收对象,它继承于CComObjectRoot,表明这是一个COM类,同时继承_IMyCarEvents接口。通常这个类看上去像:

class CSink :public CComObjectRoot,public _IMyCarEvents
{BEGIN_COM_MAP(CSink)COM_INTERFACE_ENTRY(IDispatch)COM_INTERFACE_ENTRY(_IMyCarEvents)END_COM_MAP()public:virtual ~CSink(){}STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) { return E_NOTIMPL; }STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)   { return E_NOTIMPL; }STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)  { return E_NOTIMPL; }STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr){printf("sink, id: %d, parm: %f", dispIdMember, pDispParams->rgvarg[0].fltVal);return S_OK;}
};

唯一值得注意的就是Invoke函数了,这个函数会在Proxy类中被调用。也就是通知函数。

将Sink对象挂载到对应的COM对象,就靠函数AtlAdvise。

如:

AtlAdvise(spCar, sinkptr, __uuidof(_IMyCarEvents), &cookies);

再看一下函数定义:

ATLINLINE ATLAPI AtlAdvise(_Inout_ IUnknown* pUnkCP,_Inout_opt_ IUnknown* pUnk,_In_ const IID& iid,_Out_ LPDWORD pdw)
{if(pUnkCP == NULL)return E_INVALIDARG;CComPtr<IConnectionPointContainer> pCPC;CComPtr<IConnectionPoint> pCP;HRESULT hRes = pUnkCP->QueryInterface(__uuidof(IConnectionPointContainer), (void**)&pCPC);if (SUCCEEDED(hRes))hRes = pCPC->FindConnectionPoint(iid, &pCP);if (SUCCEEDED(hRes))hRes = pCP->Advise(pUnk, pdw);return hRes;
}

从上面的代码可以看到,

1. 先从pUnkCP(也就是spCar对象)中找到IConnectionPointContainer,

2. 然后再从container中找到相应的连接点,由第三个参数指定,也就是__uuidof(_IMyCarEvents)。

3. 之后就调用连接点函数pCP->Advise(pUnk, pdw);

连接点Advise函数如下:

template <class T, const IID* piid, class CDV>
STDMETHODIMP IConnectionPointImpl<T, piid, CDV>::Advise(_Inout_ IUnknown* pUnkSink,_Out_ DWORD* pdwCookie)
{T* pT = static_cast<T*>(this);IUnknown* p;HRESULT hRes = S_OK;if (pdwCookie != NULL)*pdwCookie = 0;if (pUnkSink == NULL || pdwCookie == NULL)return E_POINTER;IID iid;GetConnectionInterface(&iid);hRes = pUnkSink->QueryInterface(iid, (void**)&p);if (SUCCEEDED(hRes)){pT->Lock();*pdwCookie = m_vec.Add(p);hRes = (*pdwCookie != NULL) ? S_OK : CONNECT_E_ADVISELIMIT;pT->Unlock();if (hRes != S_OK)p->Release();}else if (hRes == E_NOINTERFACE)hRes = CONNECT_E_CANNOTCONNECT;if (FAILED(hRes))*pdwCookie = 0;return hRes;
}

明白了,基本上就是把sink对象放到spCar对象的m_vec里面而已。

到这里,基本流程就明白了。

再总结一下:

1. 如果COM对象需要支持连接点,那么这个对象类需要从连接点和连接点容器继承下来;(一个类可以有多个连接点)

2. 创建接收对象(sink),接收对象需要从CComObjectRoot(或者类似的其他类)和连接点接口继承下来;

3. 使用AtlAdvise来将一个sink对象挂载到相应的COM对象上。(当COM对象释放的时候,会相应释放所有拥有的sink对象)

4. 这样,当COM对象需要触发一个事件的时候,就可以遍历所有sink对象,一个一个来触发。

整个过程也还是蛮简单的。其实仔细看看这个结构,这活脱脱就是一个观察者模式的典型例子。sink是观察者,具体COM对象是被观察者。当具体COM对象有事件需要触发的时候,就通过m_vec来通知所有的观察者(sink)。IConnectionPoint::m_vec是一个数组,存放所有的观察者。

代码例子:http://download.csdn.net/detail/zj510/7867453



COM连接点 - 基本原理(2)相关推荐

  1. Spring AOP:原理、 通知、连接点、切点、切面、表达式

    0:Spring AOP 原理 简单说说 AOP 的设计: 每个 Bean 都会被 JDK 或者 Cglib 代理.取决于是否有接口. 每个 Bean 会有多个"方法拦截器".注意 ...

  2. gsm在linux下工作原理,GSM基本原理

    GSM基本原理 GSM空中接口的核心技术为TDMA,技术特点:频分双工,每载频200KHZ(上,下行不对称):时分复用,每载频复用8个时分信道:慢调频,对抗干扰. 信号覆盖采用大面积部署基站,一个站点 ...

  3. Spring AOP 之 通知、连接点、切点、切面。

    1:知识背景 软件系统可以看成是由一组关注点组成的,其中,直接的业务关注点,是直切关注点.而为直切关注点提供服务的,就是横切关注点. 2:面向切面的基本原理 什么是面向切面编程 横切关注点:影响应用多 ...

  4. Computer OS系统基本原理

    Computer OS系统基本原理 第一章 绪论(考概念) 什么是OS? o 操作系统是一组控制和管理计算机软硬件资源.合理地对各类作业进行调度以及方便用户使用的程序集合. o 操作系统是位于硬件层( ...

  5. XGBoost4J-Spark基本原理

    XGBoost4J-Spark基本原理 XGBoost4J-Spark是一个项目,旨在通过使XGBoost适应Apache Spark的MLLIB框架,无缝集成XGBoost和Apache Spark ...

  6. Docker基本原理概述

    Docker基本原理概述 Docker是一个用于开发,交付和运行应用程序的开放平台.Docker能够将应用程序与基础架构分开,从而可以快速交付软件.借助Docker,可以以与管理应用程序相同的方式来管 ...

  7. 多机多卡训练基本原理

    多机多卡训练基本原理 在工业实践中,许多较复杂的任务需要使用更强大的模型.强大模型加上海量的训练数据,经常导致模型训练耗时严重.比如在计算机视觉分类任务中,训练一个在ImageNet数据集上精度表现良 ...

  8. MindSpore基本原理

    MindSpore基本原理 • MindSpore介绍 o 自动微分 o 自动并行 • 安装 o pip方式安装 o 源码编译方式安装 o Docker镜像 • 快速入门 • 文档 MindSpore ...

  9. Docker Context基本原理

    Docker Context基本原理 介绍 本指南介绍了上下文如何使单个Docker CLI轻松管理多个Swarm集群.多个Kubernetes集群和多个单独的Docker节点. 单个Docker C ...

  10. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?...

    一.垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?   1.对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址.大小以及使用情况. 通常, ...

最新文章

  1. Java8 中有趣酷炫的小技巧,你用到了那些?
  2. SAP Web Intelligence初探
  3. hdu2846 字典树(带id的)
  4. 停止FMS3.5的Apache服务
  5. java npm install_npm install不构建供应商可执行文件
  6. 如何从一个php文件向另一个地址post数据,不用表单和隐藏的变量
  7. SAP License:分摊、分配、定期重过账
  8. 斯坦福大学博士后王鸿伟: 知识图谱辅助的个性化推荐系统
  9. 如果要学习web前端,需要学习什么
  10. Qt4 QRadioButton和QCheckBox用法示例
  11. (最小生成树) Borg Maze -- POJ -- 3026
  12. Beyond Compare比较class文件
  13. NVIDIA GeForce 800系列详细配置参数
  14. windows10开启与关闭休眠模式
  15. android 6.1 app闪退,手机软件闪退怎么办 具体解决方法【图文】
  16. GIC spec之ITS和LPI中断2
  17. 算法导论------递归算法的时间复杂度求解
  18. 关于小G蛋白活化检测试剂盒
  19. Kubernetes之健康检查与服务依赖处理
  20. Sobel算子的理解

热门文章

  1. vue3.0常用的composition API
  2. 最全DNS域名解析流程及域名注册(细节!)
  3. 带无线驱动的linux版本,CentOS 5.6 上安装支持802.11b/g无线网卡驱动具体过程
  4. 腾达ac5第三方固件_腾达AC9的刷固件指南
  5. windows聚焦壁纸不更新_如何解决Win10聚焦锁屏壁纸不自动更新的问题
  6. 瞅瞅Levels.fyi发布的2020年度程序员收入报告
  7. 视频传输协议总结、码率
  8. 掘地求生是什么游戏 把主播都逼疯的玩个锤子是什么游戏-李廷学
  9. html插入图片在古诗右侧,古诗词配插图
  10. echarts wordCloud画词云图(自定义背景)