在C#代码中,你能捕获原生异常吗?

82

在 C# 代码中,您能否捕获来自某个非托管库深处抛出的本地异常?如果可以,那么是否需要采取不同的方式来捕获它,或者标准的 try...catch 可以捕获它?

9个回答

35

您可以使用Win32Exception并使用其NativeErrorCode属性来适当处理它。

// http://support.microsoft.com/kb/186550
const int ERROR_FILE_NOT_FOUND = 2;
const int ERROR_ACCESS_DENIED = 5;
const int ERROR_NO_APP_ASSOCIATED = 1155; 

void OpenFile(string filePath)
{
    Process process = new Process();

    try
    {
        // Calls native application registered for the file type
        // This may throw native exception
        process.StartInfo.FileName = filePath;
        process.StartInfo.Verb = "Open";
        process.StartInfo.CreateNoWindow = true;
        process.Start();
    }
    catch (Win32Exception e)
    {
        if (e.NativeErrorCode == ERROR_FILE_NOT_FOUND || 
            e.NativeErrorCode == ERROR_ACCESS_DENIED ||
            e.NativeErrorCode == ERROR_NO_APP_ASSOCIATED)
        {
            MessageBox.Show(this, e.Message, "Error", 
                    MessageBoxButtons.OK, 
                    MessageBoxIcon.Exclamation);
        }
    }
}

7
我相信这不是自动发生的,而只会在你使用指定了它的P/Invoke签名时抛出。而且它是基于Win32错误代码而不是Win32异常抛出的。 - Curt Hagenlocher

19

14
没错,这是真的。在比较日志文件和代码并认为自己正在失去理智的几个小时后,我自己发现了这一点。不过,显然会有一个跟进的问题,那就是你能在空的catch块中确定本地异常的类型吗?你能记录任何有用的信息来识别异常的来源吗? - Tyson
十年后...这似乎不再需要了,所以我认为catch(ex)现在可以了。 上面的FxCop链接说该规则现已被弃用,roslyn-analyzers github上相应的问题(https://github.com/dotnet/roslyn-analyzers/issues/481)说:“不再相关,因为CLR包装非Exception派生的抛出”。 - Kevin Holt

11

在C#和本地代码之间的Interop层将异常转化为托管形式,从而使其可以被你的C#代码捕获。从.NET 2.0开始,catch (Exception)应该可以捕获除不可恢复错误以外的任何异常。


3
在 .NET 1.x 中,可能会抛出一个不派生自 Exception 类的异常,但在 2.0 中默认关闭了此功能。 - Curt Hagenlocher
4
在C++中,你可以抛出任何对象,因此为了处理交互问题,能够捕获不从Exception派生的对象是很有用的。一个常见的情况是,C++程序只会抛出一个String对象(错误信息)。 - MattDavey

5
这取决于您所说的本地异常类型。如果您指的是SEH异常,则CLR将执行以下两种操作之一:
  1. 对于已知的SEH错误代码,它将将其映射到适当的.NET异常(即OutOfMemoryException)
  2. 对于无法映射(E_FAIL)或未知代码,它将仅引发SEHException实例。
这两种情况都可以使用简单的“catch(Exception)”块捕获。
另一种可能跨越本地/托管边界的本地异常类型是C++异常。我不确定它们如何映射/处理。我猜测由于Windows在SEH之上实现了C++异常,所以它们只是以相同的方式进行映射。

“Windows在SEH的基础上实现了C++异常” - 这似乎只适用于VC? - Wolf

5

我在某处使用 .NET Reflector 看到了下面的代码:

try {
  ...
} catch(Exception e) {
  ...
} catch {
  ...
}

哦,C# 不允许抛出一个不继承自 System.Exception 类的异常。而且据我所知,任何由交互操作封送程序捕获的异常都会被继承自 System.Exception 的异常类包装。

那么我的问题是是否有可能捕获一个不是 System.Exception 的异常。


1
可以发出或创建抛出任意对象的IL。C#编译器不允许您这样做,但其他编译器可能会,或者像我说的那样,您可以直接发出IL。没有类型的catch语句也将捕获任意对象以及继承Exception的对象。 - technophile

4

使用 .Net Framework 4.8,如果异常在本地代码中被良好处理, 那么你可以使用标准的try catch捕获它。

try 
{
   //call native code method
} 
catch (Exception ex) 
{
   //do stuff
} 

然而,如果本地代码在第三方dll中,你无法控制,你可能会发现开发人员无意中抛出未处理的异常。 我发现除了全局错误处理程序之外,没有任何东西可以捕获这些异常。

private static void Main()
{
    AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
    try 
    {
       //call native code method
    } 
    catch (Exception ex) 
    {
       //unhandled exception from native code WILL NOT BE CAUGHT HERE
    } 
}

private static void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    var exception = e.ExceptionObject as Exception;
    //do stuff
}

这是有原因的。未处理的本地异常可能表示您无法从中恢复的损坏状态(例如堆栈溢出或访问冲突)。但是,有些情况下,在终止进程之前仍然需要执行某些操作,例如记录刚刚尝试崩溃Windows服务的错误!

历史背景

以下内容已不再需要。
从.Net 2.0 - 3.5,您可以使用空catch:

try 
{
   //call native code method
} 
catch (Exception ex) 
{
   //do stuff
} 
catch 
{
   //do same stuff but without any exception detail
}

从.Net 4开始,默认情况下关闭了本地异常的捕获,您需要通过为方法添加属性显式打开它。

[HandleProcessCorruptedStateExceptions] 
[SecurityCritical]
private static void Main() 
{ 
    try 
    {
       //call native code method
    } 
    catch (Exception ex) 
    {
       //do stuff
    } 
}

还需要对app.config文件进行更改

<configuration>  
   <runtime>  
      <legacyCorruptedStateExceptionsPolicy enabled="true" />  
   </runtime>  
</configuration>  

3
几乎,但还差一点。你将使用

捕获异常。
try 
{
  ...
}
catch (Exception e)
{
  ...
}

但是你仍然可能会遇到潜在的问题。根据MSDN,为了确保异常析构函数被调用,你需要像这样catch异常:

try
{
  ...
}
catch
{
  ...
}

这是确保异常析构函数被调用的唯一方法(虽然我不确定为什么)。但这让你面临暴力解决与可能出现内存泄漏之间的权衡。
顺便说一下,如果你使用(Exception e)方法,你应该知道可能遇到的不同类型的异常。对于任何受管理的非异常类型(对于可以抛出字符串的语言),RuntimeWrappedException将被映射,其他异常也会被映射,例如OutOfMemoryException和AccessViolationException。COM互操作HRESULTS或除E___FAIL之外的异常将映射到COMException,最后在结尾处,你有SEHException用于E_FAIL或任何其他未映射的异常。
那么你应该怎么办呢?最好的选择是不要从你的非托管代码中抛出异常!哈哈。真的,如果你有选择,设置屏障,如果失败,则选择哪个更糟糕,即在异常处理过程中出现内存泄漏的机会,还是不知道你的异常类型。

-1

我相信一个标准的try catch应该可以解决问题。

我曾经遇到过类似的问题,当时System.data异常抛出了一个未被捕获的sqlClient异常,在我的代码中添加了try..catch后,问题得到了解决。


-2

如果您使用了

try
{

}
catch(Exception ex)
{

}

它将捕获所有异常,取决于您如何调用外部库,您可能会得到一个封装错误的 com 相关异常,但是它将捕获该错误。


7
实际上这并不完全正确,这将捕获所有符合CLS规范的异常。C++/CLI和MC++都是能够抛出非CLS兼容异常的语言。 - Peter Oehlert
1
我同意Peter的观点,我正在使用非托管DLL,并且无法捕获一些异常。 - Tilendor
20
截至我撰写此评论,该问题已获得17个赞和7个收藏,显然这是一些人正在遇到的问题,我认为人们应该在转向SO之前尝试简单的catch块。我亲眼见过异常从非托管的依赖项中直接爆炸穿过您提出的结构。我认为这个答案是不正确的。 - JohnFx
@PeterOehlert 当然,这个方法只能捕获Exception类的子类异常,但你可以使用catch(...){}来捕获其他异常,不是吗? - will
我尝试过这个方法,但它并不能捕获所有的异常。例如,我遇到了ucrtbase.dll错误0xc0000409,但它并没有被System.Exception捕获。 - Brian Colavito
显示剩余2条评论

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