在C++中捕获Python异常

4

我正在开发一个服务器-客户端应用程序,其中客户端调用服务器的API,该API为用户输入提供了Python接口。这意味着客户端界面和服务器界面都是用Python编写的,而套接字代码则是用C++编写的。

在服务器端:

我有一个名为Test的类,在C++中,这个类通过SWIG的director特性被继承到Python中命名为TestPython。 此外,我还有一个C++中的异常类MyException。

现在,TestPython类的一个函数从Python代码中抛出MyException()异常。

我想使用SWIG在C++代码中处理从Python抛出的异常。

以下是代码片段:

C++代码-

class MyException
{
   public:
     string errMsg;
     MyException();
     MyException(string);
     ~MyException();
};

class Test
{
    int value;
    public:
      void TestException(int val);
      Test(int);
};

Python 代码 -

class TestPython(Test):
   def __init__(self):
     Test.__init__(self)

   def TestException(self,val):
     if val > 20:   
       throw MyException("MyException : Value Exceeded !!!")   
     else:    
       print "Value passed = ",val

现在,如果调用TestException()函数,它应该抛出MyException。我想在我的C++代码中处理这个MyException()异常。有人能建议我该怎么做吗?我的意思是我应该在我的*.i(接口)文件中写什么来处理这个异常。上面的Python中的TestException()是由客户端调用的,因此如果服务器抛出任何异常,我必须通知客户端。

你已经查看了关于这个主题的SWIG文档吗?(http://www.swig.org/Doc2.0/SWIGDocumentation.html#Customization_exception) - gecco
你可能想要更明显地表明你正在使用SWIG的director特性 - 我第一次阅读这个问题时错过了它,而且在你展示的代码中没有任何提示。 - Flexo
@gecco - 那份文档并没有详细说明SWIG Python代码从导演中抛出的异常(即director:except)的情况。 - Flexo
1个回答

3
为了实现这个目标,您基本上需要编写一个% feature("director:except"),它可以处理Python异常并将其重新抛出为C++异常。以下是一个小而完整的示例:
假设我们要包装以下头文件:
#include <iostream>
#include <exception>

class MyException : public std::exception {
};

class AnotherException : public std::exception {
};

class Callback {
public:
        virtual ~Callback() { std::cout << "~Callback()" << std:: endl; }
        virtual void run() { std::cout << "Callback::run()" << std::endl; }
};

inline void call(Callback *callback) { if (callback) callback->run(); }

还有这个使用它的 Python 代码:

import example 

class PyCallback(example.Callback):
    def __init__(self):
        example.Callback.__init__(self)
    def run(self):
        print("PyCallback.run()")
        raise example.MyException()

callback = PyCallback()
example.call(callback)

我们可以定义以下 SWIG 接口文件:

代码如下:

%module(directors="1") example
%{
#include "example.h"
%}

%include "std_string.i"
%include "std_except.i"
%include "pyabc.i"

// Python requires that anything we raise inherits from this
%pythonabc(MyException, Exception);

%feature("director:except") {
    PyObject *etype = $error;
    if (etype != NULL) {
      PyObject *obj, *trace;
      PyErr_Fetch(&etype, &obj, &trace);
      Py_DecRef(etype);
      Py_DecRef(trace);
      // Not too sure if I need to call Py_DecRef for obj

      void *ptr;
      int res = SWIG_ConvertPtr(obj, &ptr, SWIGTYPE_p_MyException, 0);
      if (SWIG_IsOK(res) && ptr) {
        MyException *e = reinterpret_cast< MyException * >(ptr);
        // Throw by pointer (Yucky!)
        throw e;
      }

      res = SWIG_ConvertPtr(obj, &ptr, SWIGTYPE_p_AnotherException, 0);
      if (SWIG_IsOK(res) && ptr) {
        AnotherException *e = reinterpret_cast< AnotherException * >(ptr);
        throw e; 
      }

      throw Swig::DirectorMethodException();
    }
}

%feature("director") Callback;
%include "example.h"

该函数处理来自指令调用的错误,查看它是否是我们的MyException实例之一,如果是,则重新抛出指针。如果有多种类型的异常被抛出,则可能需要首先使用PyErr_ExceptionMatches来确定它是什么类型。

我们还可以使用以下方式按值或引用抛出:

  // Throw by value (after a copy!)
  MyException temp = *e;
  if (SWIG_IsNewObj(res)) 
    delete e;
  throw temp;

相反,需要注意的是,如果在Python中抛出MyException子类,则会遇到对象切片问题

我不确定代码是否100%正确-特别是我认为引用计数是正确的,但我可能错了。

注意:为了使此示例工作(否则%pythonabc将无法工作),我必须使用-py3调用SWIG。这又意味着我必须升级到SWIG 2.0,因为我安装的Python 3.2已删除了一些C-API中SWIG 1.3.40调用的已弃用函数。


能否展示一下,如果我有一个叫做 AnotherException 的异常类,它继承自 std::exception,我该如何处理异常?我应该如何区分从 Python 代码中抛出的是哪个异常,并加入哪些代码来处理这两种类型的异常。先谢谢了。 - Gunjan49
@Gunjan49 - 我已经更新了示例,展示了如何处理多种类型的异常。我认为这是最简单的方法,它使用SWIG类型系统来确定它是什么类型。如果您有一个多态类型层次结构,您可以在这里利用它。调用者无法区分异常来自哪里 - 这就是它的好处。(如果您想弄清楚它来自哪里,那么对我来说这意味着您正在做错事,但是您可以从director:except代码中设置一个标志来指示来源,如果您真的想要这样做) - Flexo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接