.net開發Ae釋放com對象的問題

來源:互聯網
上載者:User

標籤:測試   runtime   dev   finally   nike   required   ros   archive   pre   

本文轉載自:

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,主要 Interop 組件),http://msdn.microsoft.com/zh-cn/library/aax7sdch.aspx

主要 Interop 組件是一個由供應商提供的唯一的程式集。它包含用 COM 實現的類型的類型定義(作為中繼資料)。 只能有一個主要 Interop 組件,而且該程式集必須由 COM 類別型庫的發行者用強式名稱簽名。 一個主要 Interop 組件可以封裝同一類型庫的多個版本。

如果匯入為程式集的 COM 類別型庫不是由原類型庫的發行者簽名的,該類型庫不能作為主要 Interop 組件。 只有類型庫的發行者才能產生真正的主要 Interop 組件。該程式集將成為用於與基礎 COM 類別型進行互操作的正式類型定義單元。

COM 組件的發行者產生主要 Interop 組件並將它們發布給開發人員以便在 .NET Framework 應用程式中使用。 對於發行者,本節提供有關產生主要 Interop 組件的資訊。 對於開發人員,本節描述如何用主要 Interop 組件編程。

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對象的問題

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.