现在的位置: 首页 > 综合 > 正文

组件的引用计数问题

2014年11月14日 ⁄ 综合 ⁄ 共 4372字 ⁄ 字号 评论关闭

 

上面一个章我讨论了两种方式的组件使用方法。这是组件最最基本的两个问题,即简单组件的编写与使用。由于这是两个比较简单的话题,我们编写的组件也没有多大的实用价值。要想编写在实际的地理信息系统(GIS)平台软件的组件库,仅仅前面两章的内容是远远不够的。这一章我们将继续讨论我们在做COM组件是GIS的可能要用到的一些高级一点的组件技术问题。

1、组件的引用计数问题

COM组件的生命周期控制采用的是引用计数的内存管理技术。COM组件维护一个成为引用技术的数值。当客户重组件获得一个接口的时候,此引用计数将增加1。当客户用完一个组件接口的时候,组件的引用计数数值将减1。当引用计数值为0的时候,组件既可以将自己从内存中删除。当用户对某个已经获得的接口创建另外一个引用的时候,客户也要增加组件的计数数值。关于这个计数数值的维护,COM组件通过IUnknown接口的AddRef和Release方法来实现。

引用技术是使COM组件能够自行决定将自己删除的最简单同时也是最高效的方法;但同时也是COM组件开发与使用人员最容易导致恶梦的地方。因此,为了以后能够睡好觉,不管你是COM组件设计开发人员,还是COM组件使用人员,请记住下面的三条关于引用计数的简单规则[1]

1)在返回之前调用AddRef。对于那些返回接口指针的函数,在返回前应该用相应的指针调用AddRef。这些函数包括QueryInterface以及CreateInstance,同样也包括自己定义的一些返回接口指针的函数(注意:文献[1]中没有这样提,这是我们编程的习惯问题,很遗憾的是Microsoft的CComObject<T>::CreateInstance函数没有遵循这个规则,这个问题我们后面再谈)。这样当客户从这样的函数得到指针之后,它将无需调用AddRef函数。

 

2)使用完接口之后调用Release方法。在使用完某个接口之后,要记得调用Release函数。就像我们在C++中使用了new 就要记得调用delete一样,虽然COM组件的计数数值的维护是COM组件对象层面的,但对于客户而言,计数数值的维护更像是在接口层面的,即对每个接口维护一个计数数值,因此,保证接口层面的AddRef与Release的成对调用很重要。

3)在赋值之后调用AddRef。在将一个接口指针赋值给另外的一个接口指针的时候,应该调用AddRef。也就是说在建立接口的另外一个引用之后,应该增加相应组件的引用计数。这条规则在函数返回接口指针的时候与第一条规则可能会重复,这一点要注意。

这是关于引用计数的三条简单规则。下面我们来看几个例子。首先我们看看我们前面第二章用到的一个例子。

void test_com_2(void)//全局CoCreateInstance创建方式

{

     IUnknown * pUnknown=NULL;

     ICalculator * pCalculator=NULL;

     //创建对象实例,并返回IUnknown 指针,自动调用了AddRef

    HRESULT hr = CoCreateInstance(CLSID_CCalculator, NULL,

        CLSCTX_LOCAL_SERVER| CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnknown);

     if(FAILED(hr)){

         std::cout<<"组件初始化失败!";

         return ;

     }

     //通过IUnkonwn指针去查询接口指针,返回ICalculator指针,自动调用了AddRef

    hr = pUnknown->QueryInterface(IID_ICalculator,(void**)&pCalculator);

     if(FAILED(hr)){

         std::cout<<"组件初始化失败!";

         return ;

     }

     //pUnknown使用完毕,释放pUnknown指针,与CoCreateInstance函数对应。

    pUnknown->Release();

 

     //测试组件方法

     double a=2, b=3,c=0;

     //调用接口Add方法

     pCalculator->Add(2,3,&c);

     if(FAILED(hr))     {

         std::cout<<"组件调用失败!";

     }

     std::cout<<a<<"+"<<b<<"="<<c;

     //pCalculator使用完毕,释放pCalculator指针,与QueryInterface函数对应。

    pCalculator->Release();

}

这个例子中我们可以看到,CoCreateInstance函数自动调用了AddRef函数,因此,获取的pUnknown指针在使用完毕后要调用Release函数。QueryInterface函数为pCalculator指针自动调用了AddRef函数,因此在pCalculator使用完毕后,要调用Release函数。这些规则就是我们给出的第一,二条规则――“返回之前调用AddRef,使用完后调用Release”。

      下面我们再看一个例子,这个例子我们采用了ATL编程,使用了CComObject类,这也是我们必须注意的一个特例。

STDMETHODIMP CGeoXVertexSet::GetVector3d(LONG i, IGeoXVector3d** v)

{

CComObject<CGeoXVector3d>* p;

//调用CComObject<T>::CreateInstance创建GeoXVector3d组件,这个函数没有自动调用AddRef

HRESULT hr = CComObject<CGeoXVector3d>::CreateInstance(&p);

    //由于CreateInstance没有自动调用AddRef,所以立即补调AddRef

    p->AddRef();

 

     IGeoXVertex * vp = (IGeoXVertex*) m_set[i];

    Vp->AddRef();//赋值后立即调用AddRef

     double x,y,z;

     vp->get_X(&x); vp->get_Y(&y); vp->get_Z(&z);

     p->Set(x,y,z);

 

     IGeoXVertex * ppp;

     //通过p指针去查询接口指针,返回ppp指针,自动调用了AddRef

     p->QueryInterface(__uuidof(IGeoXVertex),(void**) &ppp);

     ppp->Release();//ppp使用完毕,释放ppp指针,与QueryInterface函数对应。

 

     *v = p;

    (*v)->AddRef();//赋值后立即调用AddRef,虽然这个v是要返回的,但是只能调用一次。

 

    //p使用完毕,释放p指针,与补调AddRef的对应

p->Release();

     return S_OK;

}

在这个例子中,我们使用了CComObject<T>::CreateInstance,但是如前面我们所说的,它没有按照规则一自动调用AddRef。因此我们必须在程序中立即为这个函数补充调用AddRef。其他的都是符合上面列出的三条规则的。

下面我们看看微软关于这个特例函数的实现代码,分析一下它为什么没有调用自动调用AddRef函数。

template <class Base>

HRESULT WINAPI CComObject<Base>::CreateInstance(CComObject<Base>** pp) throw()

{

     ATLASSERT(pp != NULL);

     if (pp == NULL)

         return E_POINTER;

     *pp = NULL;

 

     HRESULT hRes = E_OUTOFMEMORY;

     CComObject<Base>* p = NULL;

     //通过new直接创建了组件对象

     ATLTRY(p = new CComObject<Base>())

     if (p != NULL)

     {

         p->SetVoid(NULL);

         p->InternalFinalConstructAddRef();

         hRes = p->_AtlInitialConstruct();

         if (SUCCEEDED(hRes))

              hRes = p->FinalConstruct();

         if (SUCCEEDED(hRes))

              hRes = p->_AtlFinalConstruct();

         p->InternalFinalConstructRelease();

         if (hRes != S_OK)

         {

              delete p;

              p = NULL;

         }

     }

     *pp = p;

     return hRes;

}

我来关注上面的黑体部分代码,它在这个函数用new操作符直接创建了组件对象,在实现中成对的调用了InternalFinalConstructAddRef()和InternalFinalConstructRelease()函数,并没有实际增加组件的引用计数。如果我们没有在FinalConstruct()中增加引用计数数值,则CComObject<T>::CreateInstance() 不会为我们自动调用AddRef。因此,对于ATL的组件实现编程,我们必须制定我们自己的规则,要么在CComObject<T>:: FinalConstruct中添加组件计数,要么在调用CComObject<T>::CreateInstance()后立即补充调用AddRef函数。在微软给出的示例代码中一般是调用CComObject<T>::CreateInstance后立即调用AddRef函数。在FinalConstruct添加AddRef在进行组件包容与聚合的时候容易导致混乱。因此,我们在今后的ATL简单组件中采用在补充调用AddRef的办法。同时为了程序的效率,有些明显成对调用的AddRef和Release可以优化掉,但我们并不建议这样做,特别是对于刚开始进行COM组件编程的人员。

 

抱歉!评论已关闭.