在.NET中是否可能捕捉访问冲突异常?

35

我能做些什么来捕获AccessViolationException异常吗? 它是由一个我无法控制的非托管DLL抛出的。

5个回答

33
你不应该这样做。访问冲突是一个严重的问题:它是对无效内存地址进行意外写入(或读取)的尝试。正如 John 已经澄清的那样,在访问冲突被引发之前,未受控的 DLL 可能已经破坏了进程内存。这可能对当前进程的任何部分产生不可预测的影响。
最安全的做法是可能通知用户,然后立即退出。
更多细节:访问冲突是操作系统异常(称为 SEH 或结构化异常处理异常)。这是一种与来自 System.Exception 的托管 CLR 异常不同的异常类型。在纯粹的托管代码中很少见到 SEH 异常,但如果出现其中一个,例如在未受控的代码中,CLR 将其传递给托管代码,您也可以捕获它1
然而,捕获 SEH 异常通常不是一个好主意。更多细节在 MSDN 杂志上的文章处理损坏状态异常中有解释,以下文字摘自该文章:
“CLR 一直使用与程序本身引发的异常相同的机制将 SEH 异常传递给托管代码。只要代码不试图处理它不能合理处理的异常情况,这不是问题。大多数程序在访问冲突后无法安全地继续执行。不幸的是,CLR 的异常处理模型一直鼓励用户通过允许程序捕获 System.Exception 层次结构顶部的任何异常来捕获这些严重错误。但这很少是正确的做法。”
这在.NET 3.5之前是正确的。在.NET 4中,此行为已更改。如果您仍希望能够捕获此类异常,则需要将legacyCorruptedStateExceptionsPolicy=true添加到app.config中。更多详细信息请参见上面链接的文章。

6
澄清一下:你希望尽快退出的原因是,你不知道未经管理的 DLL 在访问冲突之前覆盖了什么内容。它可能在足够多的位置写入垃圾数据,导致你的程序无法安全地继续运行。 - John Saunders
8
我可以想到一个非常好的理由来捕捉它:如果你有一个无人值守的进程并且没有这个,那么当出现违规访问对话框时,进程将挂起而无法退出。捕获它可以让你在不弹出对话框的情况下退出。 - jpwkeeper
6
即使是通知用户或记录问题后退出,也需要捕获异常。 - Jonathan Allen
4
让大家知道,AccessViolationException并不一定意味着“写入”操作,它也可能是“读取”操作。 - WonderWorker
1
跟Noggins所说的一样,从C#中捕获异常也有很好的理由,就像在C本身中一样,在C#中也一样。有很多例子表明,服务或实用程序DLL中的错误不应该使整个系统崩溃。 - Beeeaaar

12

正如其他人指出的那样,您不应该“处理”此条件,但在开发过程中捕获此条件对于疑难解答非常方便。

您可以使用System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions属性标记您的托管方法:

[HandleProcessCorruptedStateExceptions]
public void MyMethod()
{
    try
    {
        NaughtyCall();
    }
    catch (AccessViolationException e)
    {
        // You should really terminate your application here
    }
}

1
在AutoCAD的应用程序插件上下文中,我从未到达catch块,但是主机不会崩溃,这对我的用例来说已经足够了 :) (我正在调用主机应用程序API的方法,其中发生错误,并且我不知道是否有任何预检查可以防止...) - dba

11

是的。

在您的App.config文件中,在<configuration>标记内放置以下代码:

<runtime>
    <legacyCorruptedStateExceptionsPolicy enabled="true"/>
</runtime>
现在您应该能够像处理其他异常一样捕获已损坏的状态异常(CSE)。
注意:如果您已经有一个运行时标记,那么只需将<legacyCorruptedStateExceptionsPolicy enabled="true"/>添加到其中即可。
上述方法适用于 .Net 4.5。

2
关于这个问题,我看了几篇答案,都说“在app.config中添加<legacyCorruptedStateExceptionsPolicy enabled="true"/>”,但没有人说它不应该在默认的<startup>部分中。感谢您提供<runtime>部分的提示。 - flor1an

1

首先,我完全同意0xA3的观点。但如果没有其他出路,您可以将脏的非托管dll包装在自己的进程中,并通过IPC(TCP/IP、命名管道等)传输数据。捕获所有异常并通知主进程。这样,您的主进程大多数情况下就可以免受内存损坏。


0

你可以用try-catch块来包装对非托管DLL的调用。AccessViolationExceptions 可以正常捕获。执行以下代码将显示两条消息:

try
{
    throw new AccessViolationException();
}
catch (Exception e)
{
    MessageBox.Show(e.Message + e.StackTrace, e.Message, MessageBoxButtons.OK, MessageBoxIcons.Error);
}
MessageBox.Show("Still running..");

编辑: .NET 4 引入了 行为更改,除非您明确地"要求"运行时这样做,否则无法捕获已损坏状态异常。


问题是,这样做是否明智?向用户显示友好的消息并编写日志可能是明智的选择。但是超出此范围的工作并不是一个好主意... - rioki
可能不会,除非你绝对确定没有发生任何不好的事情。 - andyp
2
那不起作用。我已经尝试捕获Exception和AccessViolationException,但它忽略了我的catch块。我认为需要一些app.config标志。 - Jonathan Allen
@Sean,通常我会说不,但现在这是在日志记录程序中失效,而我更愿意丢失日志条目,而不是整个服务。 - Jonathan Allen
4
假设你使用的是 .NET 4,那么设置应该为 legacyCorruptedStateExceptionsPolicy=true。请参考我的更新。 - Dirk Vollmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接