.Net环境下调用COM组件,垃圾回收的机制和原则?

.net的托管并不是万能的,对于有些资源如窗体、文件、位图、数据库连接都需要相应的手动回收。

.net使用的托管内存,值类型存储在堆栈上,引用类型存储在托管堆上,由GC负责垃圾回收。而COM对象使用的是内置内存,因此无法托管,需要手动释放内存。但是COM的内存管理机制是怎么样的呢?.net环境下调用COM组件,COM对象的垃圾回收应该如何进行呢,一般原则又是什么呢?这些我都不知道。

于是在ArcGIS Engine论坛上发帖求助,也没有人回答。现在把遇到的问题重新整理一下,发到博客园,希望能够得到解答。不管是自己还是别人帮助。也记录这个过程。一共发了三个帖子,如下:

1.AE进行二次开发中,COM对象的垃圾收集问题应该如何进行?

问:AE进行二次开发中,经常忽略的垃圾收集问题,AE是COM对象,垃圾收集问题应该如何进行?一般的for循环中的COM对象在什么时候释放了,还是自动释放?其他的情况应该注意哪些?
回答:我一般用Marshal.ReleaseComObject(XXX)在循环后释放部分com对象。好像还有方法可以强制进行垃圾回收,不过那样做代价太大                                        

问:http://www.cnblogs.com/3echo/archive/2008/04/20/1162304.html中的这段代码                                                                                             
for (int i = 1; i < 2500; i++)
{
      IQueryFilter qu = New QueryFilterClass();//COM对象QueryFilterClass,后记:COM对象QueryFilter对应的PIAs
       qu.WhereClause = @"Area = " + i.ToString();
      IFeatureCursor featCursor = featClass.Search(qu, true);//COM对象,释放指针
      // Use the feature cursor as required
      System.Runtime.InteropServices.Marshal.ReleaseComObject(featCursor);
  }
这样一段代码,qu对象是否需要垃圾回收?

问:http://bbs.esrichina-bj.cn/ESRI/thread-47411-1-3.html中有两处垃圾回收,前者回收了好多东西                                                                             

try{
ystem.Runtime.InteropServices.Marshal.ReleaseComObject(oField);
感觉这里有些问题,它的oField在前面New了好多次,现在在最后清理,不知道是否真的起到了完全的作用?
System.Runtime.InteropServices.Marshal.ReleaseComObject(oFields);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oFieldsEdit);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oFieldEdit);
System.Runtime.InteropServices.Marshal.ReleaseComObject(pName);
System.Runtime.InteropServices.Marshal.ReleaseComObject(pWSF);
System.Runtime.InteropServices.Marshal.ReleaseComObject(pWSName);
System.Runtime.InteropServices.Marshal.ReleaseComObject(pMemoryWS);
System.Runtime.InteropServices.Marshal.ReleaseComObject(oFeatureClass);
}
catch
{}
GC.Collect();
后面就清理了一个例子就只有System.Runtime.InteropServices.Marshal.ReleaseComObject(pFeatureCursor);

问:To fully free the COM object underlying an RCW from memory at a deterministic point, it is possible to use the ReleaseComObject method on the Marshal class, which is part of the System.Runtime.InteropServices namespace in the .NET Framework. Calling ReleaseComObject will decrease the reference count held on an RCW; once the reference count on the RCW reaches zero (which may require repeated calls to ReleaseComObject), the RCW is marked for garbage collection. If no other COM objects hold a reference to the underlying COM object at that point, the COM runtime will also clear up the COM object itself.ArcGIS Engine 帮助文档的这句话是不是就解释了前面的内容了?虽然在循环中没有每次都清除oField指向的COM对象New FieldClass对象,但是引用数已经为0,所以COM就自动释放了
后记:IQueryFilter qu = New QueryFilterClass();作用域在for循环中,当然在其他地方无法访问,但是内存管理到底是怎么回事还是不太清楚!
后来我认为:

http://bbs.esrichina-bj.cn/ESRI/thread-47411-1-3.html在For循环中的语句IQueryFilter qu = New QueryFilterClass();虽然在for循环作用域外无法访问,但是并不等于内存已经释放了:
81行的IField oField = new FieldClass();//COM对象
97行,114行的oField = new FieldClass();//COM对象

到了156行调用system.Runtime.InteropServices.Marshal.ReleaseComObject(oField);释放COM对象,及COM对象的引用数减1;此时oField引用的是114行 new FieldClass()产生的COM对象,此时81行new的和97行new的COM对象不就成垃圾内存了?

还有就是 ArcScene中加载图层,反复加载内存会一直增长。似乎处理上也有些问题。

问:先前在贴吧中问的关于COM对象的回收问题,现在让我感觉更加迷惘了!自己做了一下测试,程序中只有一个Form窗体,窗体中布局了一个MapControl和LisenceControl和一个Botton按钮,按钮的事件代码如下:
private void btnAddMap_Click(object sender, EventArgs e)
        {
            IMapDocument pMapDoc = new MapDocumentClass();//MapDocumentClass是COM对象,后记:此说法错误
            pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            IMap pMap = pMapDoc.get_Map(0);//返回COM对象MapClass的接口IMap   后记:COM对象Map对应RCW的接口IMAP
            axMapControl1.Map = pMap;
            Marshal.ReleaseComObject(pMapDoc);   //(1)
            Marshal.ReleaseComObject(pMap);       //(2)
            GC.Collect();                     //(3)
            axMapControl1.Refresh();
        }
(1)(2)(3)句代码做如下组合,a类三句代码都不添加,b添加(1)(2),c添加(3),d添加(1)(2)(3)句代码。对每种组合重复点击button按钮,这样Map就会重复加载,每次都会有MapDocumentClass和pMapDoc.get_Map(0)产生新的COM对象,第一次加载内存增长比较多可以理解。a类加载到21次左右,程序弹出错误如图(1);b种不弹出错误,点击40次没有报错,此时内存还是不断往上涨的;C中内存没有b中增长的那么迅速,在点击20次左右的时候似乎基本稳定了;d中内存增长最小,在20次左右也基本恒定了。我的数据中主要是一些Tin和一个GeodataBase中的一些要素(图2),整个数据集大小5M左右,地图文档749K;

 

这样看来只有是New的COM对象,只要没有继续引用就应该释放比较好啊!

问:依然是前面的程序,通过如下两句返回当前COM对象释放一次后的引用数n,m。                                                                                                        
int n= Marshal.ReleaseComObject(pMapDoc);
int m = Marshal.ReleaseComObject(pAct);
都调用一次,返回值为0:1,
加一句m= Marshal.ReleaseComObject(pMap);返回值为0:0
接口变量赋值和接口跳转不影响执行结果。也就是说这两者都不影响引用计数喽?
IMap pMap2 = pMap;(分别添加)
IActiveView pAct = pMap as IActiveView;(分别添加)
private void btnAddMap_Click(object sender, EventArgs e)
        {
            IMapDocument pMapDoc = new MapDocumentClass();//MapDocumentClass是COM对象
            pMapDoc.Open("D:\\红石岩演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            IMap pMap = pMapDoc.get_Map(0);//返回COM对象MapClass的接口IMap
            //IMap pMap2 = pMap;
            //IActiveView pAct = pMap as IActiveView;
            axMapControl1.Map = pMap;
           int n= Marshal.ReleaseComObject(pMapDoc);
           int m= Marshal.ReleaseComObject(pMap);
           //int m = Marshal.ReleaseComObject(pAct);
           //int m= Marshal.ReleaseComObject(pMap2);
           // m = Marshal.ReleaseComObject(pMap2);         
            GC.Collect();
            axMapControl1.Refresh();
            MessageBox.Show(n.ToString() + ":" + m.ToString());
        }

采用如下代码:IMapDocument pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            int i = Marshal.ReleaseComObject(pMapDoc);     
            pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据[url=file://\\Data\\position.mxd]\\Data\\position.mxd[/url]", "");            
            IMap pMap = pMapDoc.get_Map(0);
           axMapControl1.Map = pMap;
           int n= Marshal.ReleaseComObject(pMapDoc);
           int m = Marshal.ReleaseComObject(pMap);           
            GC.Collect();
            axMapControl1.Refresh();
            MessageBox.Show(i.ToString()+":"+n.ToString() + ":" + m.ToString());
将Button中的代码改成这样,返回值0:0:1,i值是对第一次定义的MapDocumentClass对象计数减1

以上测试了重复加载Map,出现的一些症状。后来发现自己忽略了MapDocument的Colse方法。这个方法是不是释放文件资源呢?
继续测试如下代码:
private void btnAddMap_Click(object sender, EventArgs e)
        {
            IMapDocument pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            IMap pMap = pMapDoc.get_Map(0);
            pMapDoc.Close();
            axMapControl1.Map = pMap;
           int n = Marshal.ReleaseComObject(pMapDoc);
           int m = Marshal.ReleaseComObject(pMap);
           axMapControl1.Refresh();
        }
发现内存还是一直上涨,但是没有出现资源不足的错误。但是帮助文档的解释是对MapDocument对象进行重置。
1. 测试下面的代码:
            IMapDocument pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            IMap pMap = pMapDoc.get_Map(0);
            pMapDoc.Close();
            int n = Marshal.ReleaseComObject(pMapDoc);
            int m = Marshal.ReleaseComObject(pMap);
            MessageBox.Show( n.ToString() + ":" + m.ToString());
返回值为:0:0,可以看出IMap pMap = pMapDoc.get_Map(0);增加了一次对Map的引用计数。
2. 下面的代码:
            IMapDocument pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据\\地质专题\\Untitled.mxd", "");
            IMap pMap = pMapDoc.get_Map(0);
            pMapDoc.Close();
            axMapControl1.Map = pMap;
            int n = Marshal.ReleaseComObject(pMapDoc);
            int m = Marshal.ReleaseComObject(pMap);
            MessageBox.Show( n.ToString() + ":" + m.ToString());
返回值为0:1,可以看出 axMapControl1.Map = pMap;增加一次对Map对象的引用计数。
3.下面代码:
            IMapDocument pMapDoc = new MapDocumentClass();
            pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", "");
            IMap pMap = pMapDoc.get_Map(0);
            pMapDoc.Close();
            axMapControl1.Map = pMap;
            int n = Marshal.ReleaseComObject(pMapDoc);(1)
            int m = Marshal.ReleaseComObject(pMap);(2)
            m = Marshal.ReleaseComObject(pMap);       (2)     
            GC.Collect();(3)
            axMapControl1.Refresh();
对(1)(2)(2)(3)执行组合,(1)(2),(1)(2)(2),(1)(2)(2)(3)三种组合,发现最后一种效果很明显,内存不会持续增长,会呈现波动。但是前两种内存会持续增长。难道强制GC清理的效果这么明显?产生的内存增长不是因为COM对象、Mxd文件,而是托管的内存?希望大侠解释一下。

接下文:http://www.cnblogs.com/yhlx125/archive/2011/12/13/2286108.html

posted @ 2011-11-22 11:59  太一吾鱼水  阅读(2849)  评论(5编辑  收藏  举报