C# 中的 Swig Director 异常 - 在 C++ 中获取异常文本

Swig Director Exceptions in C# - Getting the exception text in C++

我们正在使用 SWIG 3.0.3 在 Python、Java 和 C# 中创建 C++ 库的接口。

我们还在 C++ 中提供了一个 Stream 接口,并且使用 SWIG Director 功能,我们允许用户使用他们选择的任何受支持的语言来实现该接口。

问题是当在用户的 C# 流实现中抛出异常时,来自 C# 的实际错误消息丢失并且 C++ 无法检索它。

我想知道是否有解决该问题的方法。

补充信息:
1。 SWIG Java 的文档说此功能(在控制器中传递异常数据)是 SWIG 3 的新增功能。
2。我不得不稍微修改 SWIG Python 代码以获得所需的结果。

// C++ Stream Interface
class Stream 
{
    virtual uint Seek(long offset, int origin);
    // ...
};


// C# Implementation of a Stream
class CustomStream : Stream 
{
    public override uint Seek(long offset, int origin)
    {
        throw new Exception("This message should be seen in C++ caller");
        return 0;
    }
}


// C++ calling code
try 
{
    pStream->Seek(0, 0);
}
catch(std::exception e)
{
    // Here's where I want to see the exception text.
    std::cout << e.what();
}

事实证明,使用 C# 和 SWIG 很难做到这一点。我能提供的最好的方法是粗略的解决方法。

我制作了以下 header 供我们包装以证明这一点:

#include <iostream>

class Foo {
public:
  virtual ~Foo() {}

  virtual void Bar() = 0;
};

inline void test_catch(Foo& f) {
  try {
    f.Bar();
  }
  catch (const std::exception& e) {
    std::cerr << "Caught: " << e.what() << "\n";
  }
}

以及 Foo 的这个 C# 实现:

public class CSharpDerived : Foo
{
  public override void Bar()
  {
    System.Console.WriteLine("In director method");
    throw new System.Exception("This is a special message");
  }
}

如果您使用 SWIG 定位 Python,您将使用 %feature("director:except"),如 my example。 C# SWIG 语言模块似乎不支持这一点。

我们需要采用的解决此问题的策略是捕获托管异常,然后 re-throw 它作为继承自 std::exception 的东西。我们需要解决两个问题来为 C# 模拟这个:

  1. 我们如何捕获托管代码中的异常?
  2. 我们如何将 re-throw 的代码注入 SWIG 生成输出的正确位?

从托管代码捕获异常:

看起来有两种方法可以解决这个问题。

首先,如果你没有在 Linux 上使用单声道,我认为 vectored exception handlers would be able to catch this, but probably not get much information out of the exception

第二种方法是在托管代码中捕获异常。这是可以解决的,但有点像 hack。我们可以使用 csdirectorout 类型映射插入我们的代码来执行此操作:

%typemap(csdirectorout) void %{
  try {
    $cscall;
  }
  catch(System.Exception e) {
    // pass e.ToString() somewhere now
  }
%}

这里的问题是我们必须指定导向器方法的类型 - 我上面的示例仅适用于不 return 任何东西的 C++ 函数。您显然可以编写更多此类类型映射(SWIGTYPE 会是一个很好的类型映射),但其中有很多重复。

将代码作为 C++ 本机异常注入到 re-throw

这变得更丑陋了。我没有找到任何可靠的方法将代码注入生成的导演调用的 C++ 端。我希望 directorout 可以像 csdirectorout 类型映射一样作为 return 类型为 void 工作,但是对于 SWIG 3.0.2 我无法实现这一点(并在 [= 时用 `-debug-tmsearch 确认了它83=]痛饮)。甚至似乎没有任何地方可以让我们使用 pre-processor 玩个把戏,并调用宏让我们有机会添加代码。

我尝试的下一件事是让我的 csdirectorout 再次调用执行 re-throwing 的 C++ 函数,然后再完全 returned 对 C# 实现的调用。作为参考,我的尝试看起来像:

%module(directors="1") test

%{
#include "test.hh"
%}

%include <std_string.i>

%{
#include <exception>
struct wrapped_exception : std::exception {
  wrapped_exception(const std::string& msg) : msg(msg) {}
private:
  virtual const char * what () const noexcept {
    return msg.c_str();
  }
  std::string msg;
};
%}

%inline %{
  void throw_native(const std::string& msg) {
    throw wrapped_exception(msg);
  }
%}

%typemap(csdirectorout) void %{
  try {
    $cscall;
  }
  catch(System.Exception e) {
    test.throw_native(e.ToString());
  }
%}

%feature("director") Foo;

%include "test.hh"

我测试了它:

public class runme {
  static void Main() 
  {
    System.Console.WriteLine("Running");
    using (Foo myFoo = new CSharpDerived())
    {
      test.test_catch(myFoo);
    }
  }
}

因此,当我们在 C# 中捕获异常时,我们将字符串传递给另一个 C++ 函数,并最终将其作为 C++ 异常抛出,堆栈如下:

---------------------------
| throw_native()    | C++ |
| SwigDirectorBar() | C#  |
| Foo::Bar()        | C++ |
| test_catch()      | C++ |
| Main()            | C#  |
| pre-main()        | ??? |
---------------------------

但是当抛出异常时,运行时会爆炸 - 它被我使用 Mono 的 C# 实现捕获并阻止作为 C++ 异常进一步传播。

所以目前我得到的最佳解决方案是一个解决方法:让 C# 代码设置一个全局变量并在 C++ 中手动检查它,您可能会发现它已被设置,即在 SWIG 中接口:

%inline %{
char *exception_pending
%} 
%typemap(csdirectorout) void %{
  try {
    $cscall;
  }
  catch(System.Exception e) {
    test.exception_pending = e.ToString();
  }
%}

和对 test_catch() 的侵入性更改:

inline void test_catch(Foo& f) {
  try {
    f.Bar();
    check_for_csharp_exception_and_raise();
  }
  catch (const std::exception& e) {
    std::cerr << "Caught: " << e.what() << "\n";
  }
}

其中 check_for_csharp_exception_and_raise 类似于:

void check_for_csharp_excception_and_raise() {
    if (exception_pending) {
        std::string msg = exception_pending;
        delete[] exception_pending;
        exception_pending = NULL;
        throw wrapped_exception(msg);
    }  
}

我真的不喜欢这个解决方案,但似乎是目前最好的解决方案,至少不会破坏单声道兼容性并稍微修补 SWIG。