ATL中连接到连接点对象的实现

时间:2022-04-03 19:45:09
这篇文章并不是用来描述如何用ATL来开发一个带有连接点的COM组件,而是用来描述如何创建一个组件连接到某个COM组件的连接点上。如果要了解带连接点的COM组件的开发方法可以参考《ATL开发指南》和《深入解析ATL》当中的相关章节。连接到带连接点的COM组件的这些组件主要要完成的功能有两种:第一是异步调用,假设带有连接点的某个组件A有一个方法func1,func1的运算需要耗费大量的时间或者要使用大量的IO操作,为了效率A的设计者将它设计为异步的形式,也就是说客户端的程序员调用完func1以后立即返回,而事实上操作还在进行,这个时候就需要用某种方式来通知组件的客户,操作完成了,可以进行下一步操作,这个方式跟回调很像;第二种是为了对数据实现不同的操作方式,比如说组件A的func1方法会产生大量的视频数据,对这些数据的处理可以有很多方式,例如显示到某个窗口上或者保存为一个文件等等,用这样一种方式就可以把数据产生和数据处理相互独立起来,我想Direct show应该就是使用这种方式吧(注:只是知道DS里头的解码部分会用到连接点,具体怎么一回事没研究过)。

      对于第一种情况,一般是在普通程序里头使用这样的COM组件,为了得到这样的通知我们必须要实现一个连接点接口的COM组件。从关于连接点的开发知识我们知道这是一个继承自IDispatch的接口,而我们实现这个组件只是想要获得通知,所以很多东西都可以用简单的方式处理掉,不需要注册等等这些我们不关心的功能。对于这样的一个组件一般而言会是这样的一个类声明:

  1. class CMathEvent 
  2.     : public CComObjectRoot 
  3.     , public _IMathEvents 
  4.     BEGIN_COM_MAP(CMathEvent) 
  5.         COM_INTERFACE_ENTRY(IDispatch) 
  6.         COM_INTERFACE_ENTRY(_IMathEvents) 
  7.     END_COM_MAP() 
  8.  
  9. public
  10.     STDMETHODIMP GetTypeInfoCount(UINT *pctinfo) 
  11.     STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) 
  12.     STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId) 
  13.     STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
  14.     //后面是这个接口的自定义方法 
  15. }; 

作为一个COM组件那么他派生自CComObjectRoot这个基类,当然你也可以用CComObjectRootEx这个基类,这两个基类有什么区别参考《深入解析ATL》,另外这个类实现了接口_IMathEvents这个连接点。我把类声明当中实现自定义方法这部分删掉了,上面这个声明里头几个函数都是用来实现_IMathEvents的基类IDispatch的方法,由于我们只是想要达到获得通知这样一个目的,所以对于我们来说最关心的应该是Invoke这个方法,对于其他的方法我们只要简单的返回E_NOTIMPL即可。关于Invoke这个方法,参数列表里头的DISPID表明了被Dispatch的方法的ID,根据这个ID我们可以调用具体的自定义方法实现。riid和LCID分别表示接口的IID和组件的CLSID,另外两个重要的参数是pDispParam和pVarResult,前一个表示了被Dispatch的方法调用时传递进来的参数列表,对他做一些必要的检查和转换后把用它来调用真正的方法实现,而后面那个表示如果这个方法有返回值那么使用这个指针来返回。下面是一个简单的Invoke实现,只包括了很少的一部分检查

  1. STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) 
  2.     if (dispIdMember == 0x01) 
  3.     { 
  4.         VARIANT varparam; 
  5.         VariantInit(&varparam); 
  6.         VariantChangeTypeEx(&varparam,&pDispParams->rgvarg[0],lcid,0,VT_I4); 
  7.         ComputeResult(varparam.lVal); 
  8.         return S_OK; 
  9.     } 
  10.     return S_FALSE; 

ComputeResult这个函数是我定义的一个事件,具体实现很简单显示一个对话框而已,如下:

  1. STDMETHODIMP ComputeResult(LONG lResult) 
  2.     CString szMsg; 
  3.     szMsg.Format(_T("Result = %d"),lResult); 
  4.     ::MessageBox(NULL,szMsg,_T("ConnMath Callback"),MB_OK); 
  5.     return S_OK; 

只说一点,这里的CString并不是通过包含MFC库得到的,VS2005里头的ATL7已经包含了这个类,所以以后操作字符串也可以像MFC一样方便了。到这里我们要的COM组件已经实现完成了,要注意的是,因为我用的是最简单的WIN32控制台程序来实现这些东西,所以没有任何向导可以用,都是手写,没试过MFC项目不知道会不会简单一点,如果你试了请你留言告诉我有没有向导可以用。最后还有一点扫尾工作要做,那就是ATL环境,改好你的项目属性,检查一下stdafx.h里头是不是已经包含了atlbase.h和atlcom.h这两个头文件。然后再你的CPP文件里面的全局变量部分写下下面的代码

  1. CComModule _Module; 
  2.  
  3. BEGIN_OBJECT_MAP(ObjectMap) 
  4. END_OBJECT_MAP() 

最后在CoInitialize调用后面加上_Module.Init( ObjectMap, 0 )的调用,接着就是你的组件创建还有AtlAdvise的调用了,剩下的不详细讲了,上个简单的代码吧

  1. int _tmain(int argc, _TCHAR* argv[]) 
  2.     CoInitialize(NULL); 
  3.     _Module.Init( ObjectMap, 0 ); 
  4.     CComPtr<IMath> mathPtr; 
  5.     mathPtr.CoCreateInstance(__uuidof(ConnectMath)); 
  6.     CComObject<CMathEvent> *MathEventPtr; 
  7.     CComObject<CMathEvent>::CreateInstance(&MathEventPtr); 
  8.     DWORD dwCookie; 
  9.     HRESULT hr = AtlAdvise(mathPtr,MathEventPtr,__uuidof(_IMathEvents),&dwCookie); 
  10.     hr = mathPtr->Add(1,2); 
  11.     mathPtr->Minus(2,3); 
  12.     AtlUnadvise(mathPtr,__uuidof(_IMathEvents),dwCookie); 
  13.     mathPtr.Release(); 
  14.     CoUninitialize(); 
  15.     return 0; 

    对第二种情况,也就是创建一些COM组件来对数据进行不同的处理,因为本身就是要创建COM组件的,所以可以方便的使用到VS里面的向导来实现,并没有什么太大的代码量,代码的集中点都是自定义的一些实现,所以在这里也就不描述了