COM原理学习

想像一个人学习开车的过程可能是这样的:首先他要懂得一些汽车构造的基本知识,然后要学会一些基本的动作(如插拔钥匙、点火、油门、离合、刹车、转向等)。有了这些技能后,基本上可以断定他可以把车开起来了。可仅是单纯的能把车开起来,并不表示已经成为了一个合格的驾驶员。因此他除了基本技能外,还必须掌握大量交通法规和汽车维护保养知识。之后要成为一个优秀的驾驶员,他所欠缺的就是丰富的实践经验了。

 

COM的学习过程也是如此。首先我们需要通晓COM最核心最基础的理念(能把车开起来的技能),然后要了解COM世界的诸多规则(交通法规的学习),最后就是实践经验的培养。

 

接下来我们按照这个思路,逐步的进入COM的世界。

 

 

COM的原理

 

      

       COM的原理是什么?简单讲,就是利用C++的基类指针调用由派生类实现的虚函数。

 

       C++中按照COM的思路进行开发,需要对我们惯常的设计习惯做一些调整,无需过多担心,这种调整在不涉及具体的“交通规则”时,是很简单方便的,并且不需要除了C++语言外的知识。

 

       首先要明确抽象基类的概念。

C++语言中,如果一个类中含有一个纯虚函数,那么这个类就自动被归并到抽象类范畴。这个类本身是不可以实例化的,除非在它的派生类中显式实现了相关的纯虚函数,否则它的派生类也是不可以实例化的。我们对此做更严格的限制:

 

a 一个抽象基类不能包含数据成员

b 一个抽象基类不能包含非纯虚函数

c 一个抽象基类不能派生自非抽象基类

 

其次要将抽象基类应用到程序中。

我们需要按照功能分划出不同的抽象基类,每个基类声明出该类型功能需要提供的通用的函数原型。实现具体功能的类要继承自相应的抽象基类,并完成对抽象基类中所声明的函数原型的实现。

 

最后是将按照逻辑功能分类的模块,以可分发重用的形式生产出来,也就是动态链接库的理解和应用。

 

抽象基类在COM中被称为“接口”。

 

COM中有3个概念是必须在现在就理清的。那就是库,类和接口。库可以理解为动态链接库本身,通常库是可分发的最小单位。一个库包含一个或多个类,这个“类”不同于C++语言里类的概念,它指的是一个或多个逻辑上有关联的接口的集合。接口正如我们前面所讲的抽象基类,它定义了一组方法的原型。

 

COM自身制定了一堆的预定义接口,实现者必须实现其中一个或多个。正是如此繁多的接口,使COM看起来晦涩难懂。我们挑要紧的来讲,那就是IUnknown接口和IClassFactory接口。

 

IUnknown接口定义了3个方法的原型:

1)      AddRef : 引用计数增量操作

2)      Release : 引用计数减量操作,如果引用计数为零,销毁当前对象。

3)      QueryInterface : 从当前接口获得同一类对象中其它的接口指针。

 

IClassFactory接口定义了2个方法原型:

1)        CreateInstance : 创建一个类实例,并返回一个用户指定的接口指针。

2)        LockServer :       确保本服务的实现者驻留在内存中,在多次调用CreateInstance时可以获得最好的性能。

 

IUnknown接口是所有接口的祖先,除了IUnknown接口外的所有接口,都必须直接或间接派生自IUnknown。这个接口提供了引用计数原型以控制接口的生命期。

 

COM标准要求每个类(A)应该有一个实现了IClassFactory接口的类(B),由这个实现IClassFactory的类(B)负责创建类(A)的实例。那么这个类(B)又由谁创建呢?COM规定每个COM可分发对象,也就是DLL,必须实现一个导出函数DllGetClassObject,由此函数创建相应的类(B)对象实例。

 

除此之外的可选预定义接口还有很多,比如IDispatchIMonikerIPersistIConnectionPoint…..刚开始学习COM的时候就陷入这些接口陷阱是很悲惨的,所以我们不妨先绕开。

 

接下来我们用一个例子来简单实践一下。为了不涉及前面尚未提到知识,这个例子并非完全按照COM所要求的标准创建,只是为了理解COM开发的一些基本特征。

 

 

/* IUnknown 接口 */

class IUnknown

{

public:

       virtual unsigned int AddRef(void) = 0;

       virtual unsigned int Release(void) = 0;

       virtual bool QueryInterface( const char* InterfaceName,

                                                         void ** ppInterface ) = 0;

};

 

/* IClassFactory  接口*/

class IClassFactory : public IUnknown

{

public:

       virtual bool CreateInstance(const char* InterfaceName, void ** ppInterface ) = 0;

       virtual bool LockServer( BOOL bLock ) = 0;

};

 

/* 用户定义接口*/

class IEquipment : public IUnknown

{

public:

       virtual bool TurnOn(void) = 0;

       virtual bool TurnOff(void) = 0;

};

 

/* 用户接口实现A */

class CTV : public IEquipment

{

private:

       unsigned int m_ref;

public:

       CTV(void):m_ref(0){};

 

protected:

       virtual ~CTV(void){};

 

public:

       /* 继承自IUnknown */

       virtual unsigned int AddRef(void)

       {

              return ++m_ref;

       }

 

       virtual unsigned int Release(void)

       {

              unsigned int uirtn = --m_ref;

              if ( 0 == m_ref )

              {

                     delete this;

              }

              return uirtn;

       }

 

       virtual bool QueryInterface( const char* InterfaceName,

                                                         void ** ppInterface )

       {

              if ( 0 == strcmp("IUnknown", InterfaceName) )

              {

                     *ppInterface =  dynamic_cast<IUnknown * >(this);

                     ((IUnknown*)(*ppInterface))->AddRef();

              }

              else if ( 0 == strcmp( "IEquipment", InterfaceName )

              {

                     *ppInterface =  dynamic_cast<IEquipment * >(this);

                     ((IEquipment*)(*ppInterface))->AddRef();

              }

              else

              {

                     *ppInterface = NULL;

              }

 

              return NULL != *ppInterface;

       }

 

       /* 继承自IEquipment */

       virtual bool TurnOn(void)

       {

              //...Turn on TV

              return true;

       }

 

       virtual bool TurnOff(void)

       {

              //...Turn off TV

              return true;

       }

};

 

/* 类厂实现 */

class CclassFactoryObj : public IClassFactory

{

public:

       virtual bool CreateInstance(const char* InterfaceName, void ** ppInterface )

{

*ppInterface = NULL;

       CTV * _ptv = new CTV();

              If( NULL != _ptv )

{

              _ptv->AddRef();

 

       if ( _ptv->QueryInterface( InterfaceName, ppInterface ) )

{

              return true;

}

else

{

              _ptv->Release();

   _ptv = NULL;

}

}

return false;

}

 

       virtual bool LockServer( BOOL bLock )

{

       /* 不支持此操作,简单返回false */

 return false;

}

 

/* 派生自IUnknown函数的实现,略*/

};

 

为了利用类厂创建一个CTV的实例,我们提供一个单独的函数:

bool DllGetClassObject( const char * InterfaceName, void ** ppInterface )

{

       bool brtn = false;

 

       CClassFactoryObj * pobj = new CClassFactoryObj();

       If ( NULL != pobj )

{

       pobj->AddRef();

       brtn = pobj->CreateInstance( InterfaceName, ppInterface );

       pobj->Release();

       pobj = NULL;

}

 

return brtn;

}

 

客户程序可以这样使用:

……

IEquipment * pIEquipment = NULL;

if (DllGetClassObject (“Iequipment”, & pIEquipment) )

{

       pIEquipment->TurnOn();

       pIEquipment->TurnOff();

       pIEquipment->Release();

pIEquipment = NULL;

}

……

 

上述就是COM的基本思想。接下来该介绍具体的“交通规则”了。

 

COM的规则

 

1.       GUID

 

GUID是一个128位的数,用来全球唯一地标识一个库、一个类以及每一个接口。它的概念有些类似网卡的MAC地址。

 

VC附带的工具GUIDGEN.exe 可以生成GUID,这个工具位于“VC6安装目录/Common/Tools”目录下。

 

在前面提到的QueryInterface函数中,应该用接口的GUID来代替直接用字符串指定的接口名。

 

相对于COM中的类和接口,它们的GUID通常被称为CLSID IID

 

2.       返回值

 

HRESULT 是标准的COM接口函数返回值,是一个32位的整数。各位含义如下:

 

最高位:严重程度位,指示了操作成功还是失败。

29~30位:保留。

16~28位:操作码,指示了HRESULT对应于什么技术。

0~15位:信息码,在给定的严重程度和相应的技术情况下精确的结果值。

 

有两个宏可以简化对 HRESULT相关信息的检查:

HRESULT hr;

SUCCEEDED( hr )       // 如果为真,操作成功

FAILED(hr );               // 如果为真,操作失败

 

3.       类厂与对象创建

 

客户程序并不需要显式的装入DLL并调用DllGetClassObject以创建类厂。COM底层的机制会自动的完成这一切。COM提供了3API函数用于对象的创建,它们的原型如下:

HRESULT CoGetClassObject( const CLSID & clsid,

DWORD dwClsContext,

COSERVERINFO * pServerInfo,

const IID & iid,

(void**)ppv );

 

       HRESULT CoCreateInstance ( const CLSID & clsid,

                                                   IUnknown * pUnknownOuter,

                                                   DWORD dwClsContext,

                                                   const IID & iid,

                                                   (void**)ppv );

 

       HRESULT CoCreateInstanceEx( const CLSID & clsid,

                                                    IUnknown * pUnknownOuter,

                                                    DWORD dwClsContext,

COSERVERINFO * pServerInfo,

DWORD dwCount,

MULTI_QI * rgMultiQI );

 

clsid :

将要创建的对象的CLSID

 

dwClsContext :

指定组件的类别,在现阶段可以固定指定为CLSCTX_INPROC_SERVER,意思是进程内组件。

 

pServerInfo :

分布式系统使用,现阶段指定为NULL

 

iid :

创建类对象后想获得的第一个接口的IID

 

ppv :

返回iid所指定的接口指针。

 

pUnknownOuter:

    用于聚合,如果不涉及聚合,设置为NULL

 

CoCreateInstanceEx 可以在创建后返回多个接口的指针。

                                                  

4.       聚合与包容

 

聚合和包容是COM的两种重用模型。

聚合有些像C++语言里面的公共继承机制。派生类继承父类后,也就拥有了父类所有的功能。

 

包容类似C++语言里面的成员变量机制。比如一个实现某些功能的类X作为类A的一个成员变量,然后类A要实现一系列的函数,把客户的调用映射那个类X型的成员变量的相应函数上。

 

应用包容模型是非常简单的,只是重新定义调用映射的函数工作量比较大。

 

聚合模型的实现就困难得多,被聚合的COM类必须实现“一真一假”两套IUnknown接口,一套(“真”)在对象被创建时返回给创建者;另一套(“假”)把所有对自己的QueryInterace调用回传给创建者。由创建者决定返回哪个接口。

 

5.       连接点

 

COM模型决定了由客户调用COM组件的功能是很方便的,但是如果需要COM组件与客户程序进行交互,也就是实现COM组件与客户程序间的双向通信,就需要引进连接点。 连接点其实就是由客户程序实现的接口,然后客户程序把这个接口交给COM组件;COM组件在需要主动和客户程序通信的时候,使用这个接口提供的函数。有能力接受客户程序提供的连接点的COM组件,被称为可连接对象。

 

可连接对象必须实现IConnectionPointContainer接口,该接口定义如下:

 

class IconnectionPointContainer  public IUnknown

{

public:

        virtual HRESULT EnumConnectionPoints( IEnumConnectionPoints ** ) = 0;

        virtual HRESULT FindConnectionPoint( const IID * , IConnectionPoint **) = 0;

};

 

IEnumConnectionPoints称为枚举器接口,它可以枚举此可连接对象所支持的所有连接点接口。

 

 

COM最根本的原理大致如此。未涉及的部分还包括列集-散集操作原理、分布式COMDCOM )原理等。由于COM涉及面实在太广,无法提供一个比较周全的介绍。本文只对入门者必须了解的部分做了概要的讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值