首页 > 代码库 > .net开发Ae释放com对象的问题

.net开发Ae释放com对象的问题

本文转载自:

http://www.cnblogs.com/yhlx125/archive/2011/11/22/2258543.html#2269154我的博文

http://www.cnblogs.com/tendzzss/archive/2011/11/11/2245627.html

ae的com对象是需要释放的,不然就可能会锁住一些基础设备(如mdb文件等),这里研究了一下ae锁mdb的情况。

释放方法一般是,Marshal.ReleaseComObject或Marshal.FinalReleaseComObject

但要在什么时候释放com对象呢,这就需要了解dotnet跟com交互的实现方法:运行库可调用包装(RCW)。

每次将 COM 接口指针映射到该运行时可调用包装时,此引用计数都将递增。这是msdn中Marshal.ReleaseComObject 方法 描述里的一句话。那这句话是什么意思呢,什么样的操作才会导致将COM接口指针映射到运行时可调用包装。做了一个简单的实验,打开一个workspace,并打开一个featureclass,使用方法Marshal.ReleaseComObject释放COM,查看引用计数(不知道如何查看引用计数,只能通过Marshal.ReleaseComObject方法),以此来判断是否执行了将COM接口指针映射到运行时可调用包操作。

情况一:新声明一个workspace局部变量,将原来的workspace值赋给新变量,释放workspace,查看workspace引用计数

1 IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass();2 IWorkspace ws = wsf.OpenFromFile(mdbPath, 0);3 IFeatureClass fls = ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName);4 IWorkspace ws1 = ws;5 int i = Marshal.ReleaseComObject(ws);6 7 //i的值为0。

情况二:新声明一个workspace局部变量,通过IDataset.Workspace属性给新变量赋值,释放workspace,查看workspace引用计数

1 IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass();2 IWorkspace ws = wsf.OpenFromFile(mdbPath, 0);3 IFeatureClass fls = ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName);4 IWorkspace ws1 = ((IDataset)fls).Workspace;5 int i = Marshal.ReleaseComObject(ws);6 //i的值为1。

我大胆的得出结论(有可能不对):将COM接口指针映射到运行时可调用包装操作是在调用执行com对象方法并返回值时才会发生。

情况三:在一个方法里面打开featureclass,不释放workspace,新声明一个workspace局部变量,通过IDataset.Workspace属性给新变量负责,释放workspace,查看workspace引用计数

 1 private IFeatureClass getFclss(string path, string featureClassName) 2         { 3             IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass(); 4             IWorkspace ws = wsf.OpenFromFile(path, 0); 5             return ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName); 6         } 7         IFeatureClass fls = getFclss(mdbPath, featureClassName); 8         IWorkspace ws = ((IDataset)fls).Workspace; 9         int i = Marshal.ReleaseComObject(ws);10 11 //i的值为1。

情况四:跟情况三类似,不同的是调用了GC.Collect方法

1 IFeatureClass fls = getFclss(mdbPath, featureClassName);2        GC.Collect();3         IWorkspace ws = ((IDataset)fls).Workspace;4        int i = Marshal.ReleaseComObject(ws);5 6 //i的值为0。

比较情况三跟情况四,可以得出结论,释放COM对象在dotnet中的映射对象的时候,引用计数会减一

情况五:调用情况三的获取featureclass的方法,释放得到的featureclass,删除mdb文件

1 IFeatureClass fls = getFclss(mdbPath, featureClassName);2 Marshal.ReleaseComObject(fls);3 File.Delete(mdbPath);

得到“文件“。。。”正由另一进程使用,因此该进程无法访问该文件。”的异常。

情况六:修改getFclss方法,在方法内释放掉worksapce,再像情况五一样操作

 1 private IFeatureClass getFclss(string path, string featureClassName) 2         { 3             IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass(); 4             IWorkspace ws = wsf.OpenFromFile(path, 0); 5             try 6             { 7                 return ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName); 8             } 9             finally10             {11                 Marshal.ReleaseComObject(ws);12             }13         }

这时可以删除掉文件。

情况七:调用情况六修改后的getFclss,不释放featureclass,直接删除文件

1 IFeatureClass fls = getFclss(mdbPath, featureClassName);2 File.Delete(mdbPath);

得到情况五一样的异常。

情况八:调用情况六修改后的getFclss,新声明一个workspace局部变量,通过IDataset.Workspace属性给新变量赋值。

1 IFeatureClass fls = getFclss(mdbPath, featureClassName);2 IWorkspace ws = ((IDataset)fls).Workspace;

这时得到的ws是可以查看属性的,释放后的com对象查看属性会得到提示为“COM 对象与其基础 RCW 分开后就不能再使用。”的异常。

情况九:workspace打开ifeatureclass2次

1 IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass();2             IWorkspace ws = wsf.OpenFromFile(mdbPath, 0);3             IFeatureClass fcls= ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName);4             IFeatureClass fcls1 = ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName);5             int i = Marshal.ReleaseComObject(fcls);6 7 //i的值为1。 

那么对于访问COM对象属性的属性的情况会是怎么样呢。

情况十:创建getDataset方法,跟新的getFclss一样,只是返回的是IDataset,访问IDataset.Workspace,再访问IDataset.Workspace.PathName,释放workspace,查看引用计数

 1 private IDataset getDataset(string path, string featureClassName) 2         { 3             IWorkspaceFactory wsf = new AccessWorkspaceFactoryClass(); 4             IWorkspace ws = wsf.OpenFromFile(path, 0); 5             try 6             { 7                 return ((IFeatureWorkspace)ws).OpenFeatureClass(featureClassName) as IDataset; 8             } 9             finally10             {11                 Marshal.ReleaseComObject(ws);12             }13         }14         IDataset ds = getDataset(mdbPath, featureClassName);15             IWorkspace ws = ds.Workspace;16             string s = ds.Workspace.PathName;17             int i = Marshal.ReleaseComObject(ws);18 19 //i的值为1。

所以,为了避免有的对象释放漏掉,最好不要使用IDataset.Workspace.PathName这种写法。

 

 

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

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

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

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

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

 AE进行二次开发中,经常忽略的垃圾收集问题,AE是COM对象,垃圾收集问题应该如何进行?一般的for循环中的COM对象在什么时候释放了,还是自动释放?其他的情况应该注意哪些这样一段代码,qu对象是否需要垃圾回收?

1 for (int i = 1; i < 2500; i++)2 {3       IQueryFilter qu = New QueryFilterClass();//COM对象QueryFilterClass4        qu.WhereClause = @"Area = " + i.ToString();5       IFeatureCursor featCursor = featClass.Search(qu, true);//COM对象6       // Use the feature cursor as required7       System.Runtime.InteropServices.Marshal.ReleaseComObject(featCursor);8   }

 

 1 try{ 2 ystem.Runtime.InteropServices.Marshal.ReleaseComObject(oField); 3 //感觉这里有些问题,它的oField在前面New了好多次,现在在最后清理,不知道是否真的起到了完全的作用? 4 System.Runtime.InteropServices.Marshal.ReleaseComObject(oFields); 5 System.Runtime.InteropServices.Marshal.ReleaseComObject(oFieldsEdit); 6 System.Runtime.InteropServices.Marshal.ReleaseComObject(oFieldEdit); 7 System.Runtime.InteropServices.Marshal.ReleaseComObject(pName); 8 System.Runtime.InteropServices.Marshal.ReleaseComObject(pWSF); 9 System.Runtime.InteropServices.Marshal.ReleaseComObject(pWSName);10 System.Runtime.InteropServices.Marshal.ReleaseComObject(pMemoryWS);11 System.Runtime.InteropServices.Marshal.ReleaseComObject(oFeatureClass);12 }13 catch14 {}15 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循环中,当然在其他地方无法访问,但是内存管理到底是怎么回事还是不太清楚!
后来我认为: 
在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按钮,按钮的事件代码如下:

 1 private void btnAddMap_Click(object sender, EventArgs e) 2         { 3             IMapDocument pMapDoc = new MapDocumentClass();//MapDocumentClass是COM对象 4             pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); 5             IMap pMap = pMapDoc.get_Map(0);//返回COM对象MapClass的接口IMap 6             axMapControl1.Map = pMap; 7             Marshal.ReleaseComObject(pMapDoc);   //(1) 8             Marshal.ReleaseComObject(pMap);       //(2) 9             GC.Collect();                     //(3)10             axMapControl1.Refresh();11         }

 

(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
接口变量赋值和接口跳转不影响执行结果。也就是说这两者都不影响引用计数喽?

 1 IMap pMap2 = pMap;(分别添加) 2 IActiveView pAct = pMap as IActiveView;(分别添加) 3 private void btnAddMap_Click(object sender, EventArgs e) 4         { 5             IMapDocument pMapDoc = new MapDocumentClass();//MapDocumentClass是COM对象 6             pMapDoc.Open("D:\\红石岩演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); 7             IMap pMap = pMapDoc.get_Map(0);//返回COM对象MapClass的接口IMap 8             //IMap pMap2 = pMap; 9             //IActiveView pAct = pMap as IActiveView;10             axMapControl1.Map = pMap;11            int n= Marshal.ReleaseComObject(pMapDoc);12            int m= Marshal.ReleaseComObject(pMap);13            //int m = Marshal.ReleaseComObject(pAct);14            //int m= Marshal.ReleaseComObject(pMap2);15            // m = Marshal.ReleaseComObject(pMap2);         16             GC.Collect();17             axMapControl1.Refresh();18             MessageBox.Show(n.ToString() + ":" + m.ToString());19         }

 

采用如下代码:IMapDocument pMapDoc = new MapDocumentClass();
将Button中的代码改成这样,返回值0:0:1,i值是对第一次定义的MapDocumentClass对象计数减1

 1             pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); 2             int i = Marshal.ReleaseComObject(pMapDoc);      3             pMapDoc = new MapDocumentClass(); 4             pMapDoc.Open("D:\\演示数据[url=file://\\Data\\position.mxd]\\Data\\position.mxd[/url]", "");             5             IMap pMap = pMapDoc.get_Map(0); 6            axMapControl1.Map = pMap; 7            int n= Marshal.ReleaseComObject(pMapDoc); 8            int m = Marshal.ReleaseComObject(pMap);            9             GC.Collect();10             axMapControl1.Refresh();11             MessageBox.Show(i.ToString()+":"+n.ToString() + ":" + m.ToString());

 

 

以上测试了重复加载Map,出现的一些症状。后来发现自己忽略了MapDocument的Colse方法。这个方法是不是释放文件资源呢?
继续测试如下代码:

 1 private void btnAddMap_Click(object sender, EventArgs e) 2         { 3             IMapDocument pMapDoc = new MapDocumentClass(); 4             pMapDoc.Open("D:\\演示数据\\专题[url=file://\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd]\\Untitled.mxd[/url]", ""); 5             IMap pMap = pMapDoc.get_Map(0); 6             pMapDoc.Close(); 7             axMapControl1.Map = pMap; 8            int n = Marshal.ReleaseComObject(pMapDoc); 9            int m = Marshal.ReleaseComObject(pMap);10            axMapControl1.Refresh();11         }

 

发现内存还是一直上涨,但是没有出现资源不足的错误。但是帮助文档的解释是对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. 下面的代码:

1             IMapDocument pMapDoc = new MapDocumentClass();2             pMapDoc.Open("D:\\演示数据\\地质专题\\Untitled.mxd", "");3             IMap pMap = pMapDoc.get_Map(0);4             pMapDoc.Close();5             axMapControl1.Map = pMap;6             int n = Marshal.ReleaseComObject(pMapDoc);7             int m = Marshal.ReleaseComObject(pMap);8             MessageBox.Show( n.ToString() + ":" + m.ToString());

 

返回值为0:1,可以看出 axMapControl1.Map = pMap;增加一次对Map对象的引用计数。
3.下面代码:

 1             IMapDocument pMapDoc = new MapDocumentClass(); 2             pMapDoc.Open("D:\\演示数据\\地质专题[url=file://\\Untitled.mxd]\\Untitled.mxd[/url]", ""); 3             IMap pMap = pMapDoc.get_Map(0); 4             pMapDoc.Close(); 5             axMapControl1.Map = pMap; 6             int n = Marshal.ReleaseComObject(pMapDoc);(1) 7             int m = Marshal.ReleaseComObject(pMap);(2) 8             m = Marshal.ReleaseComObject(pMap);       (2)      9             GC.Collect();(3)10             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
博文的最后产生了问题:GC.Collect()显著的释放了内存,难道强制GC清理的效果这么明显?产生的内存增长不是因为COM对象、Mxd文件,而是托管的内存?

于是产生了这样的想法:MapClass、MapDocumentClass对象都是.Net托管对象,而非COM对象。事实是否如此呢?

IMapDocument pMapDoc = new MapDocumentClass();

int n = Marshal.ReleaseComObject(pMapDoc);

确实是执行了,返回值为0,说明正确释放了COM对象。如果执行下一段代码则第2句报错,说明.Net对象不能用Marshal.ReleaseComObject()方法来操作。

 1     A pa = new A(); 2     int m = Marshal.ReleaseComObject(pa); 3     MessageBox.Show(m.ToString());  4  5 class A     6 {         7  int a;         8  public int A1         9  {            10   get { return a; }            11   set { a = value; }        12  }    13 }

 

于是查找相关资料:学习了如下主题:

1. COM互操作性

2. Primary Interop Assemblies (PIAs,主互操作程序集),http://msdn.microsoft.com/zh-cn/library/aax7sdch.aspx

主互操作程序集是一个由供应商提供的唯一的程序集。它包含用 COM 实现的类型的类型定义(作为元数据)。 只能有一个主互操作程序集,而且该程序集必须由 COM 类型库的发行者用强名称签名。 一个主互操作程序集可以包装同一类型库的多个版本。

如果导入为程序集的 COM 类型库不是由原类型库的发行者签名的,该类型库不能作为主互操作程序集。 只有类型库的发行者才能产生真正的主互操作程序集。该程序集将成为用于与基础 COM 类型进行互操作的正式类型定义单元。

COM 组件的发行者生成主互操作程序集并将它们发布给开发人员以便在 .NET Framework 应用程序中使用。 对于发行者,本节提供有关产生主互操作程序集的信息。 对于开发人员,本节描述如何用主互操作程序集编程。

3. COM包装:http://msdn.microsoft.com/zh-cn/library/5dxz80y2.aspx

COM 包装(COM Wrapper)

运行时提供了包装类,使托管和非托管客户端认为它们是在其各自的环境中调用对象。 每当托管客户端对某个 COM 对象调用方法时,运行时就会创建一个运行时可调用包装 (RCW)。 RCW 的功能之一是抽取托管和非托管引用机制之间的差异。 运行时还会创建一个 COM 可调用包装 (CCW) 来逆转此过程,使 COM 客户端能够对 .NET 对象无缝地调用方法。 如下图所示,调用代码的性质将确定运行时所创建的包装类。

使用RCW,.NET客户程序就可以使用. Net对象而不是COM组件,所以不需要处理COM特性,这是由包装器来处理的。RCW隐藏了IUnknown接口和IDispatch接口并处理COM对象的引用数。(C#高级编程第六版 686页)

于是问题明了了,IMap pMap = new MapClass();这句代码使用的MapClass是Esri公司提供的PIAs表现形式,PIAs包含用 COM 实现的类型的类型定义(作为元数据)。 MapClass对象本身是.Net对象,实现了对COM对象Map的包装,即可认为传递了Map对象的引用。所以上文末尾产生的问题,调用GC.Collect()显著的释放了内存是因为释放了MapClass这个包装对象,而包装RCW对象是COM到.Net的桥梁,(数据封送处理是否可以视为产生了数据的副本?)Marshal.ReleaseComObject(pMapDoc);释放了COM对象但是没有释放托管的RCW,所以内存要等托管运行时释放。

(运行时所生成的标准 RCW 或 CCW 将为跨越 COM 和 .NET Framework 之间边界的调用提供充分的封送处理。)

4.封送处理http://msdn.microsoft.com/zh-cn/library/9f9f3yxf.aspx

 
分类: AE开发

.net开发Ae释放com对象的问题