一 問題重現(xiàn)前面在減少.NET內(nèi)存占用的一則實踐中,和大家分享了在.NET中使用P/Invoke技術來調用C++編寫的非托管代碼的例子。雖然性能和內(nèi)存占用還不錯,但是在隨后而來的幾周里,在某些同事的機器上總是偶爾會出現(xiàn)異常導致應用程序突然崩潰,尤其是在一些配置比較好的機器上。于是完善了一下日志記錄,捕捉到最多的異常是: “Attempted to read or write protected memory. This is often an indication that other memory is corrupt.” 然后調試的時候無法跟進去,直接拋出如下的異常: 根據(jù)這個異常實在查找不出任何有意義的信息,不過結合這兩者很明顯的知道,問題出在調用的非托管的代碼里面。 二 解決方法根據(jù)之前提示的問題,在Google里面查了一下,發(fā)現(xiàn)了一篇文章P/Invoke and memory related issues 該文章指出:由于.NET對內(nèi)存崩潰異常敏感,所以P/Invoke最容易出現(xiàn)內(nèi)存異常的問題。出現(xiàn)“Attempted to read or write protected memory. This is often an indication that other memory is corrupt”問題的很大一部分原因是P/Invoke非托管代碼導致的,有兩種情況下很容易出現(xiàn):
在程序中往非托管方法傳進去參數(shù)的時候,都是以String作為參數(shù)的,返回值是以StringBuilder作為類型在參數(shù)里面帶出來的,函數(shù)的返回值指示方法的執(zhí)行成功與否。傳進去的值是沒有問題,傳出的值的StringBuilder在開始調用的時候,也已經(jīng)分配了足夠大的空間。 所以就開始看是否是在非托管代碼里面是否出了異常,于是開始在C++對每個方法對進行了try catch看能否捕捉到異常,并嘗試恢復,不讓應用程序掛掉,但是發(fā)現(xiàn)C++中的異常處理并不是像.NET中的那樣,出現(xiàn)了內(nèi)存已損壞的問題,從中恢復很困難,導致程序直接崩潰。由于我對C++不太熟悉,很多方法都是我臨時拿了本書看了下寫的,所以為了徹底解決這一問題,去請教我們部門對C++比較懂的同事看了下,也沒有發(fā)現(xiàn)什么問題。 掙扎了一會兒,最后想到是不是在并發(fā)的時候出了問題,因為這個異常很容易在連續(xù)請求的時候產(chǎn)生,也很容易在配置比較好的機器上產(chǎn)生。于是想著對方法的訪問加鎖。在一開始的時候,我想著在C++里面加了鎖,后來想想,還不如直接在調用P/Invoke方法的地方加鎖。于是,解決方法很簡單,定義一個全局的鎖 public static volatile object SecuLock = new object(); 然后在所有調用同一個非托管dll里面方法的地方都加鎖,然后問題就解決了。 lock (SeverCallBack.SecuLock) { EMGFindSecu("300",result); } 三 問題的原因由于我在非托管的dll中,定義了一個全局的集合變量,然后多個方法都會對這個全局的變量進行查詢或者修改等操作,在某些特定情況下,會發(fā)生同一個dll中的兩個非托管方法會被同時調用的問題,這樣這兩個方法會同時操作一個集合對象,這時就會在C++中拋出如上異常,該異常捕捉到之后,似乎不太好恢復,所以會直接到導致應用程序出現(xiàn)崩潰。所以大家在應用P/Invoke的時候,如果出現(xiàn)如上的異常信息,不防對非托管dll方法中有可能對同一集合對象進行操作的方法加鎖,在某一時刻,只允許這些方法中的一個方法對其進行操作。 P/Invoke是.NET的一個很強大的特性,他使得我們能夠高效的和非托管代碼進行互操作,并且很容易使用,但是在使用的過程中也很容易出現(xiàn)異常,這些異常不僅難以處理和恢復,在大多數(shù)情況下會直接使得我們的應用程序崩潰。希望本文對您在.NET P/Invoke中遇到類似問題能夠提供一些幫助。 |
|
來自: 昵稱10504424 > 《C#》