引用计数

前言

COM编程——QueryInterface函数(1)中说到了IUnknown接口的声明,对于其中的QueryInterface进行了总结,而剩下的AddRef和Release并没有进行讲解,而今天这篇博文以及下一篇博文就是对AddRef和Release的总结。

生命周期控制

生命周期就是一个变量或对象存活的时间,而现在我们需要对这个时间进行控制。在实际编程时,当我们使用new开辟了一块内存,并定义了一个对象之后,我们就可以使用该对象了,当使用完成时,我们就需要使用delete释放对应的内存。new和delete就被用来控制对象的生命周期。这听起来很简单,没有什么难点,但是,在COM中,我们面对的是一个个接口,当我们使用完一个接口之后,就释放这个接口对应的组件,那么就会出现这个组件对应的其它接口也无法被使用;这不是我们希望看到的;由于我们不知道哪些接口是由同一个组件实现的,所以,我们不能在使用完一个接口之后就释放对应的组件。正是这种很难知道两个接口指针是否指向同一组件,因此决定何时可以安全地释放一个组件将是极为复杂的。得知两个接口指针是否是指向同一对象的唯一方法是查询这两个接口的IUnknown,然后对结果进行比较。当程序越来越复杂时,决定何时可以释放一个组件将是非常复杂的,甚至有的时候是不可行的。

对于上面的问题,就有了如下的方法:我们可以通知组件何时需要使用它的某个接口以及何时用完此接口,而不是直接将接口删除。一般我们总能精确地知道何时开始使用一个接口,并且可以精确地知道何时将用完此接口;就像前面说的,决定何时将用完整个组件并不是一件容易的事情。所以,当用完某个接口后给组件发个消息,告诉组件,这个接口已经使用完成了。当所有的接口使用都完成以后,对组件的释放动作就可以由组件自己来完成。

在COM中,就是使用的AddRef和Release来实现组件自己来管理其生命周期;而使用AddRef和Release来完成的是一套叫做引用计数的机制,这种机制在C++中到处可见;接下来,将对引用计数进行介绍。

引用计数简介

COM使用的是AddRef和Release实现的内存管理,归根到底用的机制就是上面说的引用计数。引用计数是使组件能够自己将自己删除的最简单同时也是最高效的方法。COM组件将维护一个引用计数的数值,在实际编程时,就是定义一个整数值。当客户从组件取得一个接口时,这个引用计数就对应的增加1;当客户使用完某个接口时,组件的引用计数就对应的减小1。当引用计数值为0时,组件就将自己从内存中删除掉。
PS:当创建某个已有接口的另外一个引用时,客户也将会增大相应组件的引用计数值。

由于我们总能精确地知道何时开始使用一个接口,并且可以精确地知道何时将用完此接口,所以实现上面所说的是不存在技术难点的。同时,通过AddRef和Release函数的名称,我们也能看出,AddRef是用来增加引用计数值的,而Release是用来减少引用计数值的。

COM就是一个规范,而在实现和使用AddRef和Release时也需要遵守以下三条规则:

  1. 在返回之前调用AddRef。对于那些返回接口指针的函数,在返回之前应该用对应的指针调用AddRef。这些函数包括QueryInterface及各式各样的CreateInstance。这样当客户从这种函数得到一个接口后,就不需要再调用AddRef;
  2. 使用完接口之后一定要记得调用Release。在使用完某个接口之后应调用此接口的Release函数;如果忘记了,就可能出现内存泄漏;
  3. 在赋值之后调用AddRef。在将一个接口指针赋给另外一个接口指针时,应调用AddRef。也就是说,在建立接口的另外一个引用之后应增加相应组件的引用计数;关于这一点,在下一篇博文中对于引用计数优化时还会讨论的,有的时候,不是必须调用AddRef的。

下面通过实际的代码来更详细、形象的说明上面的三条简单规则。

当我们使用CreateInstance之后,得到了IUnknown的指针时,由于在CreateInstance内部已经调用了,所以是不需要调用AddRef的;同理,在使用QueryInterface获得了IX接口时,也是不需要调用AddRef的;这就是上面总结的第一条规则。当在成功获得了IX接口之后,调用IX的Fx操作,操作完成之后,我们调用了Release进行对应的减少引用计数的操作,对于IUnknown也是这个道理,这就是上面总结的第二条规则。

对于第三条规则,也是许多人最容易忘记的规则,看看下面这段代码:

在代码中,我建立了对接口IX的另外一个引用pIX2,就像上面第三条规则总结的那样,我们应该相应地增加引用计数。但是,在上面的代码中,其实并不一定必须对pIX2使用AddRef和Release。在下一篇博文中,会总结到引用计数的优化。但是对于一般而言,在给某个接口指定一个别名时,都应调用AddRef,然后对应的调用Release。

AddRef和Release的实现

对于AddRef和Release的实现是非常简单的,并没有我们想象的那么复杂,下面就是一个最简单的实现代码:

但是,在实际编程中并不会像上面的那样实现的,它并不是线程安全的,可能就会出现多个线程同时访问出现混乱的情况。在实际编程中,是下面这种实现的:

InterlockedIncrementInterlockedDecrement这两个函数可以保证在同一时刻只会有同一个线程来访问成员变量。

一个完整实例

上面总结的以理论为主,现在来点干货,提供一个完整的DEMO。(工程下载

总结

AddRef和Release这种引用计数的机制,在日后的工作中可能经常会碰到,特别是做COM开发;引用计数不仅仅是一个知识点,更重要的是一种思想,一种对象的生命周期控制思想。在知道了如何去使用AddRef和Release之后,我们应该更多的去关注这种机制背后的东西,就像上面总结的,当我们无法确切的知道什么时候去释放一个对象时,我们就可以设计这种引用计数的机制去完成工作。
PS:在实际项目中,我们使用的更多的是智能指针,这就免去了手动的调用AddRef和Release;这将大大的减少出错的可能,将程序员从无穷无尽的AddRef和Release中去解放出来。

转载自http://www.jellythink.com/archives/232


目的:研究了很多微软调用COM 对象或者ActiveX控件的范例,都很少有用到Marshal.ReleaseComObject 方法。因此,对超图范例频繁使用该方法的原因产生了好奇。所以花2天时间集中研究了一下。

定义:递减所提供的运行库可调用包装的引用计数。

但实际上因为无论调用包装 COM 对象的托管客户端有多少,.net运行库可调用包装仅保留对该对象的一次引用。所以这个方法将导致.net运行库释放非托管 COM 对象上的所有引用。

(白话解释下:COM对象与.NET环境处理内存的机制不同,所以要让老的COM东东能够在新的.NET环境运行,必须给COM对象包装一下,让她打扮成.NET对象的样子。但有的东西是COM特有的,比如引用计数,这是COM对象处理内存的方法,有人调用我就+1,这样6个人在用我,引用计数=6;当计数=0的时候,表示没人用我啦,于是羞愧而自杀,从内存中消失。但.NET环境不是这样处理,所有事情都由自动啦。新老东西打交道的时候,.NET说,COM老弟,你还是自己管你自己吧。于是,在.NET环境调用COM对象,比如Layer,就必须让superMap控件自己在没人用的时候识相的走人。当然我们编程人员希望能够控制拉,这时候就是显式控制。微软提供的Marshal.ReleaseComObject 方法,就是让我们显示控制COM对象的生存。你调用这个方法,就是通过.NET环境,告诉COM对象,不用你了,马上消失。)

用途:此方法用于显式控制从托管代码使用的 COM 对象的生存期。应及时(或者在对象按指定的顺序必须释放时)使用此方法,来释放引用某些资源的基础 COM 对象。

还有隐式控制,在代码嵌套结束时自动释放。就像for循环中的int i = 0;变量一样,出了循环代码段,int i自动消失。如果你在循环中调用一个COM对象,那就不用显示控制释放。系统自动解决。在其它代码段的嵌套情况下一样。我想我看到的很多微软的范例,都是这种隐式控制的方法吧。例如很多朋友用SuperMap时会遇到时有时无的故障。在不知不觉间用了隐式控制感觉没有问题;有时候恰好调用COM的代码没有隐式处理,所以出现故障。

COM对象与.NET的交互是牵扯到多方面的知识,是一个复杂的庞大话题。有兴趣的话可以MSDN研究下。在与COM对象打交道的时候,最好把更多的精力投入到体会其体系结构方面。

转载自:http://blog.youkuaiyun.com/HeavensDoor/article/details/3647228


我们知道.Net中对于内存的管理有两种方式,一种是托管对象管理,另一种是非托管对象管理。而我们通常理解的内存管理就是GC(垃圾收集),虽然GC通过对托管堆的管理,可以使我们有机会从繁锁的诸如内存泄漏之类的问题中解放出来,可以将精力专注于程序的逻辑上。但是将所有的事情都交给GC有时会损及程序的效率,严重的甚至会导致错误。

        为什么会出现这种情况呢?问题在于对非托管资源(文件句柄)或者需要特别关照的对象(Bitmap)对象等,GC表现得就有点差强人意了(这句话或许说得并不正确,因为微软设计GC的本意就是用来针对托管对象管理)。如何有效地利用GC来进行内存管理,如何对程序的性能进行优化不是本文讨论的范围。本文的要旨在于AE开发中如何来释放非托管对象。

一、AOUninitialize.Shutdown

        很多时候我们都会遇到这种情况:在退出AE应用程序中,常常提示这样的错误:“The instruction x references memory at x. The memory could not be read”。出现这种错误的原因主要在于COM对象(非托管对象)没有释放,在我们结束使用它的进程的时候,它阻止我们正常释放它,它释放的优先级高于当前使用它的进程释放的优先级,也就是说进程释放之前,必须先释放掉它。

        明白了上面的原理,那么解决这一问题的方法便非常简单,我们只需在应用程序退出之前调用ESRI.ArcGIS.ADF.COMSupport.AOUninitialize.Shutdown()方法即可释放非托管对象了(9.2之前的版本AoUninitialize并不在ESRI.ArcGIS.ADF.COMSupport命名空间下,需注意)。

二、Marshal.ReleaseComObject

        NET开发中,引用COM对象主要是通过RCW(Runtime Callable Wrappers)机制来实现的(有点类似于代理模式)。对于COM对象的释放,GC表现得有点无能为力,因此必须在程序中显示释放掉COM对象占用的资源,否则将会出现一些意想不到的错误。比如重复地从Personal GeoDataBase中打开GeoDataBase Cursors而又没有及时释放,将会引发“No more tables can be opened.”其它情形中,你可能会发现应用程序退出时,COM对象依然在内存中引用。比如StyleGallery如果没有显示释放,在应用程序退出时就会引发错误。

1、Releasing StyleGallery:

  1. private void MyFunction() 
  2. {
  3. IStyleGallery styCls = new StyleGalleryClass() as IStyleGallery;
  4. // Use the StyleGalleryClass here ...
  5. int refsLeft = 0;
  6. do
  7. {
  8.     refsLeft = Marshal.ReleaseComObject(styCls);
  9. }
  10. while (refsLeft > 0);
  11. }
复制代码


2、Releasing geodatabase cursors

  1. for (int i = 1; i < 2500; i++)
  2. {
  3.       IQueryFilter qu = New QueryFilterClass();
  4.        qu.WhereClause = @"Area = " + i.ToString();
  5.       IFeatureCursor featCursor = featClass.Search(qu, true);
  6.       // Use the feature cursor as required
  7.       System.Runtime.InteropServices.Marshal.ReleaseComObject(featCursor);
  8.   }
复制代码


3、Releasing WebObject(ArcGIS Server)
  1. private void doSomthing_Click(object sender, System.EventArgs e)
  2. {
  3. using (WebObject webobj = new WebObject())
  4. {
  5.     ServerConnection serverConn = new ServerConnection("doug", true);
  6.     IServerObjectManager som = serverConn.ServerObjectManager;
  7.     IServerContext ctx = som.CreateServerContext("Yellowstone","MapServer");
  8.     IMapServer mapsrv = ctx.ServerObject as IMapServer;
  9.     IMapServerObjects mapo = mapsrv as IMapServerObjects;
  10.     IMap map = mapo.get_Map(mapsrv.DefaultMapName);
  11.     IFeatureLayer flayer = map.get_Layer(0) as IFeatureLayer;
  12.     IFeatureClass fclass = flayer.FeatureClass;
  13.     IFeatureCursor fcursor = fclass.Search(null, true);
  14.     webobj.ManageLifetime(fcursor);
  15.     IFeature f = null;
  16.     while ((f = fcursor.NextFeature()) != null)
  17.     {
  18.       // do something with the feature
  19.     }
  20.     ctx.ReleaseContext();
  21. }
  22. }
复制代码



三、什么时候应该释放非托管对象?

        这个问题非常重要,当我们确信在 释放 之后不再调用非托管对象资源时候,就可以调用System.Runtime.InteropServices.Marshal.ReleaseComObject()方法进行显式 释放 了,否则将会引发新的错误。


转载自:http://bbs.esrichina-bj.cn/ESRI/viewthread.php?tid=27432&highlight=%CA%CD%B7%C5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值