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 中真的很难做到。我能提供的最好的是粗略的解决方法。
我制作了以下标题供我们包装以演示这一点:
#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# 的这个:
- 如何从托管代码中捕获异常?
- 我们如何注入代码以重新投入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。