How do I propagate C++ exceptions to Python in a SWIG wrapper library?

后端 未结 6 1132
天涯浪人
天涯浪人 2020-12-13 04:54

I\'m writing a SWIG wrapper around a custom C++ library which defines its own C++ exception types. The library\'s exception types are richer and more specific than standard

相关标签:
6条回答
  • 2020-12-13 05:05

    I know this question is a few weeks old but I just found it as I was researching a solution for myself. So I'll take a stab at an answer, but I'll warn in advance it may not be an attractive solution since swig interface files can be more complicated than hand coding the wrapper. Also, as far as I can tell, the swig documentation never deals directly with user defined exceptions.

    Let's say you want to throw the following exception from your c++ code module, mylibrary.cpp, along with a little error message to be caught in your python code:

    throw MyException("Highly irregular condition...");  /* C++ code */
    

    MyException is a user exception defined elsewhere.

    Before you begin, make a note of how this exception should be caught in your python. For our purposes here, let's say you have python code such as the following:

    import mylibrary
    try:
        s = mylibrary.do_something('foobar')
    except mylibrary.MyException, e:
        print(e)
    

    I think this describes your problem.

    My solution involves making four additions to your swig interface file (mylibrary.i) as follows:

    Step 1: In the header directive (the usually unnamed %{...%} block) add a declaration for the pointer to the python aware exception, which we will call pMyException. Step 2 below will define this:

    %{
    #define SWIG_FILE_WITH_INIT  /* for eg */
    extern char* do_something(char*);   /* or #include "mylibrary.h" etc  */
    static PyObject* pMyException;  /* add this! */
    %}
    

    Step 2: Add an initialization directive (the "m" is particularly egregious, but that's what swig v1.3.40 currently needs at that point in its constructed wrapper file) - pMyException was declared in step 1 above:

    %init %{
        pMyException = PyErr_NewException("_mylibrary.MyException", NULL, NULL);
        Py_INCREF(pMyException);
        PyModule_AddObject(m, "MyException", pMyException);
    %}
    

    Step 3: As mentioned in an earlier post, we need an exception directive - note "%except(python)" is deprecated. This will wrap the c+++ function "do_something" in a try-except block which catches the c++ exception and converts it to the python exception defined in step 2 above:

    %exception do_something {
        try {
            $action
        } catch (MyException &e) {
            PyErr_SetString(pMyException, const_cast<char*>(e.what()));
            SWIG_fail;
        }
    }
    
    /* The usual functions to be wrapped are listed here: */
    extern char* do_something(char*);
    

    Step 4: Because swig sets up a python wrapping (a 'shadow' module) around the .pyd dll, we also need to make sure our python code can 'see through' to the .pyd file. The following worked for me and it's preferable to editing the swig py wrapper code directly:

    %pythoncode %{
        MyException = _mylibrary.MyException
    %}
    

    It's probably too late to be of much use to the original poster, but perhaps somebody else will find the suggestions above of some use. For small jobs you may prefer the cleanness of a hand-coded c++ extension wrapper to the confusion of a swig interface file.


    Added:

    In my interface file listing above, I omitted the standard module directive because I only wanted to describe the additions necessary to make exceptions work. But your module line should look like:

    %module mylibrary
    

    Also, your setup.py (if you are using distutils, which I recommend at least to get started) should have code similar to the following, otherwise step 4 will fail when _mylibrary is not recognized:

    /* setup.py: */
    from distutils.core import setup, Extension
    
    mylibrary_module = Extension('_mylibrary', extra_compile_args = ['/EHsc'],
                        sources=['mylibrary_wrap.cpp', 'mylibrary.cpp'],)
    
    setup(name="mylibrary",
            version="1.0",
            description='Testing user defined exceptions...',
            ext_modules=[mylibrary_module],
            py_modules = ["mylibrary"],)
    

    Note the compile flag /EHsc, which I needed on Windows to compile exceptions. Your platform may not require that flag; google has the details.

    I have tested the code using my own names which I have converted here to "mylibrary" and "MyException" to help in generalizing the solution. Hopefully the transcription errors are few or none. The main points are the %init and %pythoncode directives along with

    static PyObject* pMyException;
    

    in the header directive.

    Hope that clarifies the solution.

    0 讨论(0)
  • 2020-12-13 05:06

    example.h

    struct MyBaseException : public std::runtime_error {
      MyBaseException(const std::string& msg)
        : std::runtime_error{msg} {}
    };
    
    struct MyDerivedException : public MyBaseException {
      MyDerivedException(const std::string& msg)
        : MyBaseException{msg} {}
    };
    
    void foo() { throw MyBaseException{"my base exception"}; }
    void bar() { throw MyDerivedException{"my derived exception"}; }
    void baz() { throw std::runtime_error{"runtime error"}; }
    void qux() { throw 0; }
    

    example.i

    %module example
    %{
    #include "example.h"
    %}
    
    %include <std_string.i>
    %include <exception.i>
    
    %exception {
      try {
        $action
      } catch (const MyDerivedException& e) {
         PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyDerivedException), e.what());
         SWIG_fail;
      } catch (const MyBaseException& e) {
         PyErr_SetString(SWIG_Python_ExceptionType(SWIGTYPE_p_MyBaseException), e.what());
         SWIG_fail;
      } catch(const std::exception& e) {
        SWIG_exception(SWIG_RuntimeError, e.what());
      } catch(...) {
        SWIG_exception(SWIG_UnknownError, "");
      }
    }
    
    %exceptionclass MyBaseException;
    
    %include "example.h"
    

    test.py

    import example
    
    try:
      example.foo()
    except example.MyBaseException as e:
      print(e)
    
    try:
      example.bar()
    except example.MyDerivedException as e:
      print(e)
    
    try:
      example.baz()
    except RuntimeError as e:
      print(e)
    
    try:
      example.qux()
    except:
      print("unknown error")
    

    python3 test.py

    my base exception
    my derived exception
    runtime error
    unknown error
    
    0 讨论(0)
  • 2020-12-13 05:11

    Is the swig exception documentation any help? It mentions defining different exception handlers..

    0 讨论(0)
  • 2020-12-13 05:15

    U can also use:

    catches: http://www.swig.org/Doc3.0/SWIGPlus.html#SWIGPlus_catches

    Example:

    %catches(std::exception, std::string, int, ...);
    

    which generates for each function a try catch block:

      try {
        result = (namespace::Function *)new namespace::Function ((uint16_t const *)arg1);
      }
      catch(std::exception &_e) {
        SWIG_exception_fail(SWIG_SystemError, (&_e)->what());
      }
      catch(std::string &_e) {
        SWIG_Python_Raise(SWIG_From_std_string(static_cast< std::string >(_e)), "std::string", 0); SWIG_fail;
      }
      catch(int &_e) {
        SWIG_Python_Raise(SWIG_From_int(static_cast< int >(_e)), "int", 0); SWIG_fail;
      }
      catch(...) {
        SWIG_exception_fail(SWIG_RuntimeError,"unknown exception");
      }
    
    0 讨论(0)
  • 2020-12-13 05:16

    From the swig documentation

    %except(python) {
    try {
    $function
    }
    catch (RangeError) {
        PyErr_SetString(PyExc_IndexError,"index out-of-bounds");
        return NULL;
    }
    }
    
    0 讨论(0)
  • 2020-12-13 05:23

    I'll add a bit here, since the example given here now says that "%except(python)" is deprecated...

    You can now (as of swig 1.3.40, anyhow) do totally generic, script-language-independent translation. My example would be:

    %exception { 
        try {
            $action
        } catch (myException &e) {
            std::string s("myModule error: "), s2(e.what());
            s = s + s2;
            SWIG_exception(SWIG_RuntimeError, s.c_str());
        } catch (myOtherException &e) {
            std::string s("otherModule error: "), s2(e.what());
            s = s + s2;
            SWIG_exception(SWIG_RuntimeError, s.c_str());
        } catch (...) {
            SWIG_exception(SWIG_RuntimeError, "unknown exception");
        }
    }
    

    This will generate a RuntimeError exception in any supported scripting language, including Python, without getting python specific stuff in your other headers.

    You need to put this before the calls that want this exception handling.

    0 讨论(0)
提交回复
热议问题