C#+ArcEngine中com對象的釋放問題


1、問題描述

  最近在寫C#下AE的開發,在循環獲取數據並修改時碰到了兩個問題“超出系統資源”和“超出打開游標最大數”;在網上看了一些資料,發現都是說在循環中沒有釋放已經使用過的對象,但是在循環中實際上是有為com對象賦值為null的,但是還是沒法解決。后來想着將對象賦值為null和marshal是不是效果不一樣,就特意寫了一個簡單的循環來測試,代碼如下(初級代碼,比較亂,請輕噴):

 1 public void Test_釋放游標方式()
 2         {
 3             string ssName = "controlpoint";//圖層名稱(里面有4w多條數據)
         //測試兩種釋放方式的執行時間
4 DateTime tmStart = DateTime.Now; 5 DisplayString("開始於:"+tmStart.ToLongTimeString()+"\r\n"); 6 DateTime tmEnd; 7 DateTime tmMiddle1; 8 DateTime tmMiddle2; 9 TimeSpan ts, tsMax;
         //具體執行代碼
10 IFeatureWorkspace pFeaWs = mc_Ws as IFeatureWorkspace;//我已經建立好的SDE工作空間對象 11 IFeatureClass pFeaCls = pFeaWs.OpenFeatureClass(ssName); 12 string strWhere = string.Format("stationserieseventid in ('f9bd16ed-ae2a-454c-9eba-7123dc41af28','7e3d0d4a-8c5e-49b5-8977-e060cd4cef6d','a89300a5-3503-4976-b5d2-3d5a712f7b36')"); 13 IFeatureCursor pCur = null; 14 IFeature pFea = null; 15 IQueryFilter pFilter = new QueryFilterClass(); 16 int numCurHasBuild = 0; 17 //int counsts = pFeaCls.FeatureCount(null);//總要素個數 18 try 19 { 20 tmStart = DateTime.Now; 21 DisplayString("開始於:" + tmStart.ToLongTimeString()+"\r\n"); 22 tsMax = TimeSpan.MinValue;
23 int idxStart = 45809; 24 int idxEnd = 90416; 25 int idxTmpNode = idxStart + 1000 26 for (int idx = idxStart; idx < idxTmpNode; idx++) 27 { 28 tmMiddle1 = DateTime.Now; 29 strWhere = "objectid = '" + idx.ToString() + "'"; 30 pFilter.WhereClause = strWhere;
             //獲取游標對象
31 pCur = pFeaCls.Search(pFilter, false);//如果游標對象沒有釋放,那么一次循環不能超過280,否則會爆‘超出打開游標最大數’錯誤 32 numCurHasBuild++;
             //循環獲取游標內的要素
33 pFea = pCur.NextFeature(); 34 while (pFea != null) 35 { 36 string tmp = pFea.get_Value(1).ToString(); 37 pFea = pCur.NextFeature(); 38 } 39 //pCur = null;//像這樣,對象實際上是沒有釋放的;依舊會在283條的時候報錯 40 Marshal.ReleaseComObject(pCur);//這種方式可以完全釋放掉對象,此時可以完全循環完4w條數據
              
41 localReleaseComObj(pCur);//自己寫的一個方法,達到釋放游標pCur的目的 42 if (pCur != null) 43 localReleaseComObj(pCur);

45 tmMiddle2 = DateTime.Now; 46 ts = tmMiddle2 - tmMiddle1; 47 if (ts > tsMax) 48 tsMax = ts; 49 } 50 tmEnd = DateTime.Now; 51 DisplayString("循環中耗時最多的一次時間為:"+tsMax.TotalSeconds+"\r\n"); 52 DisplayString("執行完一輪循環;消耗的總時間為:"+(tmEnd-tmStart).TotalSeconds+"\r\n"); 53 } 54 catch (Exception ex) 55 { 56 DisplayString("在第" + numCurHasBuild.ToString() + "處發生錯誤!\r\n" + ex.Message); 57 throw new Exception(ex.Message); 58 } 59 }

2、試驗過程

  在測試中,專門打開一個控制點圖層(里面有4w多條數據),然后根據條件循環建立游標獲取對象;

情況一:在使用完游標之后,將其賦值為null;這時候對象實際上是沒有被釋放掉的,因此在283次循環時就會報錯‘ora 超出打開游標最大數’;

情況二:在使用完游標時,在循環末尾,用marshal.releasecomobject方法來釋放com對象,這時循環可以走完4w次;我認為這時對象是完全被釋放掉了的。

同時在使用marshal.releasecomobject方法時,並沒有增加多余的時間,循環執行時間上還是跟不釋放對象一樣(小數據量比較)。

3、結論

  在使用ArcEngine中的游標對象時,一定要在使用完之后進行對象的釋放,否則會不定時出現上面的錯誤;而且需要使用marshal.releasecomobject方法來進行對象的釋放,賦值為null是達不到目的的;

  另外說一下“超出系統資源”的這個錯誤,這個在最初沒有釋放對象時,每次當數據量超過200就會不定時報這個錯誤,一直也沒法解決;但是當我在循環中加上marshal.releasecomobject方法釋放對象后,這個問題竟然莫名消失了(后來特意試了4000條數據,也可以通過測試)。相當於解決“超出打開游標最大數”的問題時,順帶着讓另一個問題也消滅了(可能是我不理解其間的具體原理,有待深入)。

到這里,ArcEngine對象釋放問題已經闡述完畢。以下是在解決過程中碰到的另一個問題,看官可以選擇性忽略。

 Other-補充問題(問題3:“COM對象與其基礎RCW分開后就不能再使用”)

   如代碼中黃色部分所示,在這里我碰到了一個很奇怪的問題,即在上面一句我已經用Marshal釋放了pCur對象,但是后面仍然可以繼續使用(指的是再次用Marshal釋放)而不報錯。在這里我是將pCur作為參數傳遞進方法內部,然后再次使用Marshal釋放,我一共嘗試了以下幾種方式:

①普通的參數傳遞:可以正常使用,並Marshal而不報錯;

②ref參數:也可以正常使用並Marshal而不報錯

③params參數:此時不能正常使用,在調用方法時就會報錯“com對象與其基礎rcw分開后不能再使用”,而且連方法內部都進不去。

  經過一天的查找原因,我認為原因如下:

  首先,params是數組型參數,我們一般是想達到分散傳遞任意個數的參數的目的;此時,我們看似傳遞的是一個個的參數,但是編譯器在進入方法之前會做一項工作,即使用我們傳遞的這些分散的參數建立一個一維數組,然后將這個數組傳遞給方法進行后續的操作(注意:錯誤也就是在這一步出現,就是我們連方法都進不去就報錯的原因)。

  其次,因為C#中數組是引用類型,在建立數組時是同時進行堆和棧上的內存分配的,也即數組的實際內容是在堆上存儲,而在棧上開辟一塊內存用於保存數組名(即堆中保存實際數據的內存的地址)。這就是C#中引用類型變量建立的方式:引用類型的地址存放在棧上,而實際對象存儲在堆上。

  第三,ArcEngine的com對象建立就遵循上面的原則,及同時使用了堆和棧(這部分由.NET的rcw為我們做了,具體原理現在還不清楚);因此我們在C#實際使用ArcEngine的對象時就像普通的引用類對象一樣。而當我們使用Marshal將建立的對象釋放后(比如IFeatureCursor),相當於斬斷了堆和棧之間的聯系橋梁;

  第四,而我們在Marshal過pCur對象一次之后,再像上面黃色代碼的那一部分來調用pCur時,普通參數、ref參數都不會去尋找pCur所對應的在堆上的對象(形象一點就是都不會走這一段已經被斬斷了的橋梁);但是params數組型參數不同,編譯器在進入方法之前是需要進行一步數組建立的操作的,而在建立數組時是需要同時操作堆和棧的,這時候再去走那一段已經被斬斷了的橋梁就無法通過,也就會報錯“com與其基礎rcw分開后不能再使用”。【這一步你也可以變換一種方式測試:即建立一個直接以數組作為參數的方法,然后使用時事先建立數組,再把數組作為參數傳遞。這時候你會發現,數組是無法建立成功的,報錯就出現在這里

  結論:這里params數組參數的語法糖給我們了一個小坑,如果不去理解堆、棧的概念,還有方法的參數傳遞與使用,就會覺得這個錯誤報的莫名其妙;而當深入理解了C#中方法的參數類型、傳遞,以及引用類型對象的原理,就能理解這個錯誤。【以上只是我個人查找資料后的理解,總感覺理解了出錯的原因,但是沒法清楚的表達出來;還有關於最后堆、棧的問題,我理解的很淺,如果有不對的地方希望各位大神能指出來,我再修改】


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM