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

本文关键字:异常 C++ 文本 获取 中的 Swig Director | 更新日期: 2023-09-27 18:36:46

我们正在使用 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 Director 异常 - 获取C++中的异常文本

事实证明,

这在 C# 和 SWIG 中真的很难做到。我能提供的最好的是粗略的解决方法。

我制作了以下标题供我们包装以演示这一点:

#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";
  }
}

而这个 C# 实现 Foo:

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"),就像我的例子一样。不过,C# SWIG 语言模块似乎不支持此功能。

我们需要采用的解决此问题的策略是捕获托管异常,然后将其重新抛出为继承自std::exception的东西。我们需要解决两个问题来模拟 C# 的这个:

  1. 如何从托管代码中捕获异常?
  2. 我们如何注入代码以重新投入SWIG生成的输出的正确位?

从托管代码中捕获异常:

看起来有两种方法可以做到这一点。

首先,如果你没有在 Linux 上使用 mono,我认为向量异常处理程序将能够捕获这一点,但可能不会从异常中获得太多信息。

第二种方法是捕获托管代码中的异常。这是可以解决的,但有点黑客。我们可以插入我们的代码来使用 csdirectorout typemap 来做到这一点:

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

这里的缺点是我们必须指定 Director 方法的类型 - 我上面的例子仅适用于不返回任何内容的C++函数。你显然可以写更多这样的类型图(SWIGTYPE会是一个很好的类型),但是有很多重复

注入代码以作为C++本机异常重新引发

这变得更加丑陋。我没有找到任何可靠的方法将代码注入生成的控制器调用的C++端。我预计 directorout 可以像 csdirectorout typemap 一样作为返回类型为 void,但在 SWIG 3.0.2 中,我无法做到这一点(并在运行 SWIG 时使用 '-debug-tmsearch 确认)。我们似乎甚至没有任何地方可以使用预处理器来玩弄技巧并调用宏,从而有机会添加代码。

我尝试的下一件事是在完全返回对 C# 实现的调用之前,让我的csdirectorout再次调用重新抛出的 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。