COM笔记 - 可连接对象

发布时间:2012-12-30 18:19
分类名称:COM


概念

入接口(incoming interface)    接口由COM组件定义,由客户发动调用,提供方是COM组件。

出接口(outgoing interface)    接口由COM组件定义,由COM组件发动调用,提供方是客户。

接收器(sink)        实现COM组件的出接口的COM对象,此对象在客户端实现。

                此COM对象不需要CLSID和类厂,其他特征和普通COM对象类似。

 

可连接对象(connectable object)/ 源对象(Source)    支持一个或者多个出接口的COM对象。

 

 

客户和可连接对象的关系图

 

从图中可以看出来,客户是发起方,1. 客户先和COM建立联系,2. 然后将接收器对象的接口指针传给COM对象。

 

COM对象和客户俩种对应关系(一对多,多对一)

规范'可连接对象',实现通用

如何管理可连接对象和接收器的对应关系,以达到通用的目的?

那肯定就是定义一些必要的接口,定义一些必要的功能,双方都依赖接口,实现通用。

 

可连接对象定义了俩个接口,来管理连接:

  1. 一个可连接对象可以有多个出接口。

    IConnectionPointContainer 来管理多个出接口(IConnectionPoint)。

  2. 每个出接口又可以对应多个客户的接收器,对应多个接收器,相当于对应的是多个'连接'。

    一个IConnectionPoint对应一个接口,IConnectionPoint来管理多个连接。

 

可连接对象基本结构

 

IConnectionPointContainer : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE EnumConnectionPoints(
/* [out] */ IEnumConnectionPoints **ppEnum) = 0;

virtual HRESULT STDMETHODCALLTYPE FindConnectionPoint(
/* [in] */ REFIID riid,
/* [out] */ IConnectionPoint **ppCP) = 0;

};

IConnectionPoint : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetConnectionInterface(
/* [out] */ IID *pIID) = 0;

virtual HRESULT STDMETHODCALLTYPE GetConnectionPointContainer(
/* [out] */ IConnectionPointContainer **ppCPC) = 0;

virtual HRESULT STDMETHODCALLTYPE Advise(
/* [in] */ IUnknown *pUnkSink,
/* [out] */ DWORD *pdwCookie) = 0;

virtual HRESULT STDMETHODCALLTYPE Unadvise(
/* [in] */ DWORD dwCookie) = 0;

virtual HRESULT STDMETHODCALLTYPE EnumConnections(
/* [out] */ IEnumConnections **ppEnum) = 0;

};

可连接对象需要实现这两个接口,然后客户就能使用它们来和COM进行连接通讯。

 

枚举器

从上面俩个接口中可以看到获取到俩个枚举器:IEnumConnectionPoints , IEnumConnections。而且接口成员基本上是一样的(Out 参数有些区别)。

typedef IConnectionPoint *LPCONNECTIONPOINT;

IEnumConnectionPoints : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next(
/* [in] */ ULONG cConnections,
/* [length_is][size_is][out] */ LPCONNECTIONPOINT *ppCP,
/* [out] */ ULONG *pcFetched) = 0;

virtual HRESULT STDMETHODCALLTYPE Skip(
/* [in] */ ULONG cConnections) = 0;

virtual HRESULT STDMETHODCALLTYPE Reset( void) = 0;

virtual HRESULT STDMETHODCALLTYPE Clone(
/* [out] */ IEnumConnectionPoints **ppEnum) = 0;

};

 

typedef struct tagCONNECTDATA

{

IUnknown *pUnk;

DWORD dwCookie;

}     CONNECTDATA;

 

typedef struct tagCONNECTDATA *PCONNECTDATA;

typedef struct tagCONNECTDATA *LPCONNECTDATA;

IEnumConnections : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Next(
/* [in] */ ULONG cConnections,
/* [length_is][size_is][out] */ LPCONNECTDATA rgcd,
/* [out] */ ULONG *pcFetched) = 0;

virtual HRESULT STDMETHODCALLTYPE Skip(
/* [in] */ ULONG cConnections) = 0;

virtual HRESULT STDMETHODCALLTYPE Reset( void) = 0;

virtual HRESULT STDMETHODCALLTYPE Clone(
/* [out] */ IEnumConnections **ppEnum) = 0;

};

COM中枚举器的模板为:

class IEnum<ELT_T> : public IUnknown

{

public:

virtual HRESULT Next(
ULONG celt,
ELT_T rgcd,
ULONG *pcFetched) = 0;
virtual HRESULT Skip(ULONG celt) = 0;
virtual HRESULT Reset(void) = 0;

virtual HRESULT Clone(IEnum<ELT_T> **ppenum) = 0;

};

 

建立连接过程

发起方(操作驱动源):客户。

加上源对象支持出接口ISomeEventSet,客户实现了出接口(ISomeEventSet),指针:pSomeEventSet。

 

  1. 客户调用pUnk->QueryInterface(IID_IConnectionPointContainer, &pConnectionPointContainer),调用成功说明COM对象为可连接对象。失败则为不可连接。
  2. pConnectionPointContainer->FindConnectionPoint(IID_ISomeEvnetSet, pConnectionPoint),若调用失败,则说明对象不支持出接口(ISomeEventSet)。无论成功否,都应该调用pConnectionPointContainer->Release。
  3. pConnectionPoint->Advise(pSomeEventSet, &dwCookie)。pSomeEventSet为接收器,将接收器指针传递给COM对象的连接点对象(实现IConnetionPoint接口的对象),建立了一个连接。
  4. 客户取消连接时,调用pConnectionPoint->Unadvise(dwCookie),并调用pConnectionPoint->Release,释放连接点对象。

上面的过程出现了三个对象:一个接收器对象,由客户端实现。一个实现了IConnectionPointContainer接口的对象和实现了IConnectionPoint接口对象,由COM对象实现。(实际开发中,使用MFC/ATL开发,框架已经实现好了这俩个对象,我们不用做处理,这就是定义这俩个接口的好处)。

 

可以看到,整个过程并没有需要提供CLSID和类厂。原因就在于接收器对象是由客户端实现的,并且传给了COM。发起方一直是客户。COM不需要调用诸如CoCreateInstance去和客户通讯创建对象。代码实现:

 

ISomeEventSet *gpSomeEventSet;

… …

// Initialize

CSomeEventSet *pSink = new CSomeEventSet;

pSink->QueryInterface(IID_ISomeEventSet, pSomeEventSet);

… …

// connect the sink object to the connectable object we have

hr = pConnectionPoint->Advise(pSomeEvent, &pSomeEvent->m_dwCookie);

… …

// disconnect the sink object from the connectable object we have

hr = pConnectionPoint->Unadvise(pSomeEvent->m_dwCookie);

… …

pSink->Release();

 

事件激发和处理

通常,事件激发有"源对象(可连接对象)"的入接口激发。

 

问题:以上都是客户和COM预先约定好出接口。如果客户和COM是分开开发的,客户并不知道COM支持什么出接口,如何处理?

可以通过IConnectionPointContainer和IConnectionPoint俩个接口,只能得到COM出接口的IID。并得不到出接口的声明信息。即便可能得到接口信息,还需要根据接口信息动态创建出对象,然后建立连接,这些操作并不简单。现在难点有俩个:

  1. 获取接口信息。
    1. 使用IProvideClassInfo,ITypeInfo
  2. 动态创建对象。
    1. 自己实现
    2. 使用IDispatch作为出接口

 

获取接口信息,客户首先向源对象请求IProvideClassInfo接口,获得接口指针,调用GetClassInfo,获得ITypeInfo接口指针。

 

IProvideClassInfo : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetClassInfo(
/* [out] */ ITypeInfo **ppTI) = 0;

};

ITypeInfo : public IUnknown
{
public:
virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetTypeAttr(
/* [out] */ TYPEATTR **ppTypeAttr) = 0;

virtual HRESULT STDMETHODCALLTYPE GetTypeComp(
/* [out] */ ITypeComp **ppTComp) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetFuncDesc(
/* [in] */ UINT index,
/* [out] */ FUNCDESC **ppFuncDesc) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetVarDesc(
/* [in] */ UINT index,
/* [out] */ VARDESC **ppVarDesc) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetNames(
/* [in] */ MEMBERID memid,
/* [length_is][size_is][out] */ BSTR *rgBstrNames,
/* [in] */ UINT cMaxNames,
/* [out] */ UINT *pcNames) = 0;

virtual HRESULT STDMETHODCALLTYPE GetRefTypeOfImplType(
/* [in] */ UINT index,
/* [out] */ HREFTYPE *pRefType) = 0;

virtual HRESULT STDMETHODCALLTYPE GetImplTypeFlags(
/* [in] */ UINT index,
/* [out] */ INT *pImplTypeFlags) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [size_is][in] */ LPOLESTR *rgszNames,
/* [in] */ UINT cNames,
/* [size_is][out] */ MEMBERID *pMemId) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ PVOID pvInstance,
/* [in] */ MEMBERID memid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetDocumentation(
/* [in] */ MEMBERID memid,
/* [out] */ BSTR *pBstrName,
/* [out] */ BSTR *pBstrDocString,
/* [out] */ DWORD *pdwHelpContext,
/* [out] */ BSTR *pBstrHelpFile) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetDllEntry(
/* [in] */ MEMBERID memid,
/* [in] */ INVOKEKIND invKind,
/* [out] */ BSTR *pBstrDllName,
/* [out] */ BSTR *pBstrName,
/* [out] */ WORD *pwOrdinal) = 0;

virtual HRESULT STDMETHODCALLTYPE GetRefTypeInfo(
/* [in] */ HREFTYPE hRefType,
/* [out] */ ITypeInfo **ppTInfo) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE AddressOfMember(
/* [in] */ MEMBERID memid,
/* [in] */ INVOKEKIND invKind,
/* [out] */ PVOID *ppv) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE CreateInstance(
/* [in] */ IUnknown *pUnkOuter,
/* [in] */ REFIID riid,
/* [iid_is][out] */ PVOID *ppvObj) = 0;

virtual HRESULT STDMETHODCALLTYPE GetMops(
/* [in] */ MEMBERID memid,
/* [out] */ BSTR *pBstrMops) = 0;

virtual /* [local] */ HRESULT STDMETHODCALLTYPE GetContainingTypeLib(
/* [out] */ ITypeLib **ppTLib,
/* [out] */ UINT *pIndex) = 0;

virtual /* [local] */ void STDMETHODCALLTYPE ReleaseTypeAttr(
/* [in] */ TYPEATTR *pTypeAttr) = 0;

virtual /* [local] */ void STDMETHODCALLTYPE ReleaseFuncDesc(
/* [in] */ FUNCDESC *pFuncDesc) = 0;

virtual /* [local] */ void STDMETHODCALLTYPE ReleaseVarDesc(
/* [in] */ VARDESC *pVarDesc) = 0;

};


使用IDispath作为出接口的模型