我们正在使用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();
}
答案 0 :(得分:1)
事实证明这对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";
}
}
这个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")
使用std::exception
。 C#SWIG语言模块似乎并不支持这一点。
我们需要采用的策略来捕获托管异常,然后将其重新抛出为继承自%typemap(csdirectorout) void %{
try {
$cscall;
}
catch(System.Exception e) {
// pass e.ToString() somewhere now
}
%}
的东西。我们需要解决两个问题来为C#模拟这个:
看起来有两种解决方法。
首先,如果你不在Linux上使用mono,我认为my example能够捕捉到这一点,但可能不是vectored exception handlers。
第二种方法是捕获托管代码中的异常。这是可以解决的,但有点像黑客。我们可以使用csdirectorout typemap插入我们的代码来执行此操作:
csdirectorout
这里的bodge是我们必须指定导演方法的类型 - 我上面的例子仅适用于不返回任何内容的C ++函数。显然你可以写更多这些类型图(SWIGTYPE会很好),但是那里有很多重复。
这变得更加丑陋。我没有找到任何可靠的方法将代码注入到生成的Director调用的C ++端。我希望将directorout作为返回类型与csdirectorout类型映射一起工作,但是使用SWIG 3.0.2我无法实现这一点(并在运行SWIG时使用`-debug-tmsearch确认)。我们甚至无法在任何地方使用预处理器播放技巧,并调用一个宏,让我们有机会添加代码。
我接下来尝试的是让我的%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"
再次调用一个C ++函数进行重新抛出,然后再调用C#实现完全返回。作为参考,我的尝试看起来像:
public class runme {
static void Main()
{
System.Console.WriteLine("Running");
using (Foo myFoo = new CSharpDerived())
{
test.test_catch(myFoo);
}
}
}
我测试了它:
---------------------------
| throw_native() | C++ |
| SwigDirectorBar() | C# |
| Foo::Bar() | C++ |
| test_catch() | C++ |
| Main() | C# |
| pre-main() | ??? |
---------------------------
因此,当我们在C#中捕获异常时,我们将字符串传递给另一个C ++函数,最后我们将它作为C ++异常抛出,其中包含如下堆栈:
%inline %{
char *exception_pending
%}
%typemap(csdirectorout) void %{
try {
$cscall;
}
catch(System.Exception e) {
test.exception_pending = e.ToString();
}
%}
但是当异常被抛出时,运行时会爆炸 - 它被我使用Mono的C#实现捕获并被阻止作为C ++异常进一步传播。
所以目前我得到的最佳解决方案是解决方法:让C#代码设置一个全局变量并在C ++中手动检查它,你可能会发现它已被设置,即在SWIG中接口:
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);
}
}
类似于:
{{1}}
我真的不喜欢这个解决方案,但似乎是现在提供的最好的东西,至少在没有破坏单声道兼容性和修补SWIG的情况下。