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 oViolatorLib.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();