是否可以使用委托函数从非托管代码中引发.Net异常?

Mar*_*vin 7 .net c# mono unmanaged exception

我在SO周围搜索并发现了各种相关的问题,其中一些基本上回答"不要这样做".

我想调用一些访问各种现有C++代码的非托管C++代码.现有代码可能有各种错误条件,我想映射到C#异常.通过在Java和JNI中执行类似操作,似乎可能有一个委托函数来引发已定义的异常,然后可以直接从非托管代码调用它们.然后调用看起来像(csharp) - >(非托管) - >(csharp委托,抛出/设置挂起异常)然后返回.

下面的代码似乎工作正常(vs2010,单声道).我的问题是这种方法有什么问题 - 例如规范说在调用非托管代码或线程问题等之后,异常并不能保证仍然是"待定"的......

// unmanaged.cpp 
#include <cstdio>
#define EXPORT __declspec(dllexport)
#define STDCALL __stdcall

typedef void (STDCALL* raiseExcpFn_t)(const char *);
extern "C" {
  // STRUCT ADDED TO TEST CLEANUP
  struct Allocated {
     int x;
     Allocated(int a): x(a) {}
     ~Allocated() {
    printf("--- Deleted allocated stack '%d' ---\n", x);
    fflush(stdout);
    }
  };

  static raiseExcpFn_t exceptionRaiser = 0;
  EXPORT void STDCALL registerRaiseExcpFn(raiseExcpFn_t fun) {
      exceptionRaiser = fun;
  }
  EXPORT void STDCALL hello(const char * x) {
    Allocated a0(0); 
    try {
      Allocated a1(1);
      printf("1 --- '%s' ---\n", x); fflush(stdout);
      (*exceptionRaiser)("Something bad happened!");
      printf("2 --- '%s' ---\n", x); fflush(stdout);
    } catch (...) {
      printf("3 --- '%s' ---\n", x); fflush(stdout);
      throw;
    }
    printf("4 --- '%s' ---\n", x); fflush(stdout);
  }
}

// Program.cs
using System;
using System.Runtime.InteropServices;

class Program {
  [DllImport("unmanaged.dll")]
  public static extern void registerRaiseExcpFn(RaiseException method);

  [DllImport("unmanaged.dll")]
  public static extern void hello([MarshalAs(UnmanagedType.LPStr)] string m);
  public delegate void RaiseException(string s);
  public static RaiseException excpfnDelegate = 
    new RaiseException(RaiseExceptionMessage);

  // Static constructor (initializer)
  static Program() { 
    registerRaiseExcpFn(excpfnDelegate);
  }

  static void RaiseExceptionMessage(String msg) {
    throw new ApplicationException(msg);
  }

  public static void Main(string[] args) {
    try {   
      hello("Hello World!");
    } catch (Exception e) {
      Console.WriteLine("Exception: " + e.GetType() + ":" + e.Message);
    } 
  }
}
Run Code Online (Sandbox Code Playgroud)

更新:更正了测试和输出,显示单声道和Windows(带/ EHsc)泄漏

// Observed output // with Release builds /EHa, VS2010, .Net 3.5 target
//cstest.exe
// --- Deleted allocated stack '0' ---
// --- Deleted allocated stack '1' ---
// 1 --- 'Hello World!' ---
// 3 --- 'Hello World!' ---
// Exception: System.ApplicationException:Something bad happened!

// Observed LEAKING output // with Release builds /EHsc, VS2010, .Net 3.5 target
// cstest.exe
// 1 --- 'Hello World!' ---
// Exception: System.ApplicationException:Something bad happened!

// LEAKING output DYLD_LIBRARY_PATH=`pwd` mono program.exe 
// 1 --- 'Hello World!' ---
// Exception: System.ApplicationException:Something bad happened!
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 4

是的,只要在 Windows 上运行代码就可以实现这一点。C++ 异常和 .NET 异常都构建在 Windows 提供的本机 SEH 支持之上。然而,在 Linux 或 Apple 操作系统上,您不会有这样的保证,但是,当您使用 Mono 时,这是一个问题。

使用正确的设置构建 C++ 代码非常重要,MSVC++ 编译器使用优化来避免在发现代码永远不会引发 C++ 异常时注册异常过滤器。这在您的情况下不起作用,您的 RaiseException 委托目标将抛出一个异常,并且编译器没有机会猜测这一点。您必须使用 /EHa 进行编译,以确保在堆栈展开时调用 C++ 析构函数。您将在此答案中找到更多详细信息。