System.AccessViolationException異常通常發生在非托管代碼嘗試從尚未分配的內存讀取或寫入內存時。
制造錯誤
class Program { static void Main(string[] args) { try { var intPtr = new IntPtr(1); Marshal.WriteByte(intPtr, 1); } catch (AccessViolationException e) { Console.WriteLine(e); } } }
在大多數情況下(至少在我的經驗中),通過使用DllImport
調用C++代碼時會引發Access違規事件。我記得在一天內創建了.NET程序,通過舊的C++ API將代碼連接到電話。代碼在很大程度上依賴於在非托管代碼中調用方法,處理AccessViolationException是編寫程序的一個必需部分。我想這就是你試圖與舊的buggy代碼集成得到的結果:D
這確實使用DllImport
重新創建異常,我編寫了下面的相當錯誤的C++代碼:
#include "pch.h" #include "Violator.h" void ViolateMe() { int* data1 = 0; *data1 = 0; }
Visual Studio 2019甚至在生成代碼時創建警告:
6011: Dereferencing NULL pointer 'data1'.
要從C#調用這個,我使用以下代碼:
class Program { static void Main(string[] args) { try { ViolateMe(); } catch (AccessViolationException e) { Console.WriteLine(e); } } [DllImport(@"C:\path\to\ViolatorLib.dll")] private static extern int ViolateMe(); }
class Program { [HandleProcessCorruptedStateExceptions] static void Main(string[] args) { ... } ... }
<?xml version="1.0" encoding="utf-8" ?> <configuration> ... <runtime> <legacyCorruptedStateExceptionsPolicy enabled="true"/> </runtime> ... </configuration>
微軟似乎建議不要添加這種配置,但我不太同意。他們這樣做的原因是,這實際上是一個嚴重錯誤,可能會導致應用程序關閉。但您總是想告訴用戶發生了錯誤,並將異常記錄到錯誤日志中。至於.NET Core,HandleProcessCorruptedStateExceptionsAttribute類在框架中,但似乎不會導致捕獲AccessViolationException。我很想聽聽人們在.NET Core中對此的體驗。
調試這個錯誤
老實說,AccessViolationException可能是調試的噩夢。在大多數情況下,錯誤是在您無權訪問的非托管代碼中產生的。讓我們深入到不同的場景中,討論如何在每種情況下威脅異常。
當您有權訪問非托管代碼時
讓我們繼續上面的例子。在這種情況下,我實際上可以訪問失敗的C++代碼。如果使用F11進入了ValueTeMeE方法,您會注意到VisualStudio只是跳過C++代碼,直接轉到catch塊。這是由於本機代碼調試作為默認設置被禁用所致。若要解決此問題,請右鍵單擊C#項目,單擊“屬性”並選擇“調試”選項卡。在調試器引擎下面,請確保選中“啟用本機代碼調試”(或單擊CTRL+t)。當項目被保存並啟動應用程序時,VisualStudio現在在C++代碼內部中斷:
能夠看到導致此錯誤的確切行和變量是一個很大的幫助。
當您無法訪問非托管代碼時
好家伙。你一直在讀,因為你無法訪問失敗的代碼,對吧?在這種情況下,除了與代碼的維護人員聯系之外,沒有其他事情可做。為了幫助他們,可以生成應用程序的內存轉儲。為此,請通過Visual Studio運行應用程序,並等待異常發生。然后單擊調試|將轉儲另存為。。。並命名文件。非托管代碼的開發人員可以使用類似WinDbg的工具來嘗試找出發生了什么。
既然我已經提到了WinDbg,你可能會想“為什么不自己檢查一下WinDbg有什么問題?”好問題。這是絕對可能的。WinDbg確實幫我省了一兩次。不過,使用WinDbg與典型的.NET開發人員所需的技能相去甚遠。如果對它有普遍的需求,我會寫一些東西。現在,您只需要知道可以使用Visual Studio創建進程內存轉儲。
使用WebBrowser控件時
Windows窗體和WPF都附帶了一個捆綁的web瀏覽器組件,簡單地稱為web browser。我既經歷過AccessViolationException,也看到很多人在這種情況下抱怨這個異常。
瀏覽器組件在過去是相當麻煩的。近年來,我相信大多數問題都是由線程問題引起的。C#中的Web控件通常用於生成某種URL的無頭快照。我的意思是,這些天你生產了多少具有嵌入式瀏覽器功能的Windows窗體應用程序?
在線程中創建WebBrowser控件時,請確保在線程上設置正確的ApartmentState:
var thread = new Thread(() => { var br = new WebBrowser(); br.Navigate(url); }); thread.SetApartmentState(ApartmentState.STA); thread.Start();