【问题标题】:Can you catch a native exception in C# code?您可以在 C# 代码中捕获本机异常吗?
【发布时间】:2010-09-14 02:43:02
【问题描述】:

在 C# 代码中,您能否捕获从某个非托管库的深处抛出的本机异常?如果是这样,您需要做任何不同的事情来捕捉它还是标准的 try...catch 得到它?

【问题讨论】:

    标签: c# .net exception


    【解决方案1】:

    对于 .Net Framework 4.8 IF,本机代码中的异常是 handled nicely,然后您可以使用标准的 try catch 来捕获它。

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

    但是,如果本机代码位于您无法控制的第 3 方 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 开始,它们 turned off 原生异常能够被默认捕获,您需要通过使用属性装饰您的方法来显式地将其重新打开。

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

    app.config 文件还需要change

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

    【讨论】:

      【解决方案2】:

      这取决于您所谈论的本机异常类型。如果您指的是 SEH 异常,则 CLR 将执行以下两项操作之一。

      1. 如果是已知的 SEH 错误代码,它会将其映射到适当的 .Net 异常(即 OutOfMemoryException)
      2. 在不可映射 (E_FAIL) 或未知代码的情况下,它只会抛出一个 SEHException 实例。

      这两个都将被一个简单的“catch (Exception)”块捕获。

      另一种可以跨越本地/托管边界的本地异常是 C++ 异常。我不确定它们是如何映射/处理的。我的猜测是,由于 Windows 在 SEH 之上实现了 C++ 异常,它们只是以相同的方式映射。

      【讨论】:

      • Windows implements C++ exceptions on top of SEH -- 这似乎只适用于 VC?
      【解决方案3】:

      差不多,但不完全。您将使用

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

      但您仍然会遇到潜在的问题。根据MSDN,为了确保调用异常析构函数,您必须像这样捕获:

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

      这是确保调用异常析构函数的唯一方法(尽管我不确定为什么)。但这会让您在蛮力与可能的内存泄漏之间进行权衡。

      顺便说一句,如果您使用 (Exception e) 方法,您应该知道您可能遇到的不同类型的异常。 RuntimeWrappedException 是任何托管非异常类型将被映射到的对象(对于可以抛出字符串的语言),其他类型将被映射,例如 OutOfMemoryException 和 AccessViolationException。 COM 互操作 HRESULTS 或除 E___FAIL 之外的异常将映射到 COMException,最后您将获得 E_FAIL 的 SEHException 或任何其他未映射的异常。

      那你该怎么办?最好的选择是不要从您的非托管代码中抛出异常!哈。确实,如果您有选择,设置障碍和失败会使选择更糟,在异常处理期间有可能发生内存泄漏,或者不知道您的异常是什么类型。

      【讨论】:

        【解决方案4】:

        不带 () 的捕获将捕获不符合 CLS 的异常,包括本机异常。

        try
        {
        
        }
        catch
        {
        
        }
        

        有关详细信息,请参阅以下 FxCop 规则 http://msdn.microsoft.com/en-gb/bb264489.aspx

        【讨论】:

        • 是的,这是真的。在花费了特别烦人的几个小时比较日志文件和代码并认为我正在丢失它之后,我自己才发现了这一点。不过,显而易见的后续问题是,您能否从空的 catch 块中确定本机异常的类型?您可以记录任何有用的信息来确定异常的来源吗?
        • 10 年后...这似乎不再需要了,所以我认为 catch(ex) 现在可以了。上面的 FxCop 链接说该规则现在已弃用,roslyn-analyzers github (github.com/dotnet/roslyn-analyzers/issues/481) 上的相应问题说“不再相关,因为 CLR 包装了非异常派生的抛出”
        【解决方案5】:

        您可以使用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);
                }
            }
        }
        

        【讨论】:

        • 我相信这不是自动的,只有当您使用指定它的 P/Invoke 签名时才会抛出。它是基于 Win32 错误代码引发的,而不是 Win32 异常。
        【解决方案6】:

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

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

        嗯,C# 不允许抛出不是从 System.Exception 类派生的异常。据我所知,interop marshaller 捕获的任何异常都由继承 System.Exception 的异常类包装。

        所以我的问题是是否有可能捕获不是 System.Exception 的异常。

        【讨论】:

        • 可以发出或以其他方式创建抛出任意对象的 IL。 C# 编译器不会让你这样做,但其他编译器可能会,或者像我说的那样你可以直接发出 IL。没有类型的 catch 语句将捕获任意对象以及继承 Exception 的对象。
        【解决方案7】:

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

        我遇到了一个类似的问题,System.data 异常抛出了一个未捕获的 sqlClient 异常,在我的代码中添加一个 try..catch 在实例中起到了作用

        【讨论】:

          【解决方案8】:

          C# 和本机代码之间的互操作层会将异常转换为托管形式,使其能够被您的 C# 代码捕获。从 .NET 2.0 开始,catch (Exception) 应该捕获除不可恢复错误之外的任何内容。

          【讨论】:

          • 在 .NET 1.x 中,可能会抛出不是从 Exception 类派生的异常,但在 2.0 中默认关闭此功能
          • 在 C++ 中,您可以抛出任何对象,因此对于互操作问题,能够捕获不是从异常派生的对象很有用。一个常见的场景是 C++ 程序只会抛出一个字符串(错误消息)。
          【解决方案9】:

          如果你使用

          try
          {
          
          }
          catch(Exception ex)
          {
          
          }
          

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

          【讨论】:

          • 无论如何,您都无法捕获 StackOverflow 或 OutOfMemoryException,对吗?
          • 那些是结束错误,会停止应用程序,所以是的,你不能使用它们。
          • 这实际上并不完全正确,这将捕获所有符合 CLS 的异常。 C++/CLI 和 MC++ 都是能够引发非 CLS 兼容异常的语言。
          • 我同意彼得的观点,我正在使用非托管 DLL,但我无法捕获一些异常。
          • 当我写这篇评论时,这个问题有 17 个赞和 7 个喜欢,所以这显然是一个正在发生在人们身上的问题,我认为人们会在求助之前尝试一个简单的 catch 块到 SO。当异常来自非托管依赖项时,我个人已经看到异常通过您提出的构造爆炸。我不认为这个答案是正确的。
          猜你喜欢
          • 2014-02-20
          • 1970-01-01
          • 2012-08-10
          • 2012-07-17
          • 1970-01-01
          • 1970-01-01
          • 2011-12-17
          • 1970-01-01
          • 2020-11-20
          相关资源
          最近更新 更多