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# 模拟这个:
- 我们如何捕获托管代码中的异常?
- 我们如何将 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。
我们正在使用 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# 模拟这个:
- 我们如何捕获托管代码中的异常?
- 我们如何将 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。