[C++-sig] Wrapper for exception translation - resurrection? :)

Piotr Jaroszyński p.jaroszynski at gmail.com
Wed Nov 21 20:48:43 CET 2007


Hello again,

For those, who don't read #boost all the time, the idea is to allow exposing 
C++ exceptions like that:

class_<CppException, bases<PyBaseException> > (...) 
and probably:
exception<CppException, bases<PyBaseException> > (...)
which would do the same thing as class_ and also register some default 
exception translator.

First thing I did was injecting a Python exception as a base in new_class 
(object/class.cpp). That was rather easy and the type I get back from the 
      object result = object(class_metatype())(name, bases, d);
seems fine, afair it is exactly the same as the one I get from 
PyErr_NewException function. But new_class isn't the whole story. There are a 
few things done after it, which also modify the type (including the change of  
tp_init, which seems to be important).
To test this new stuff I have created a simple module exposing a basic class 
and an exception translator:

    // Our C++ exception (PyExc_BaseException base is forced in the new_class)
    bp::class_<Foo> ("Foo", bp::no_init).def_readonly("msg", &Foo::msg);
    // function that raises Foo
    bp::def("cpp_raise", &cpp_raise);
    // translator
    bp::register_exception_translator<Foo>(&translator);

void translator(const Foo & f)
{
    // Make a new "good" exception type using the PyErr_NewException
    PyTypeObject * good_et((PyTypeObject*)PyErr_NewException("exception.exc", 
PyExc_BaseException, 0));
     
    // Fetch our type registered in boost.python
    PyTypeObject * bad_et(
        bp::converter::registered<Foo>::converters.get_class_object());
    
    // Approach 1
    // Get the tp_init from good_et - otherwise PyEval_CallObject fails
    bad_et->tp_init = good_et->tp_init;
    // Create the exception instance
    PyObject * bad_e(PyEval_CallObject((PyObject*)bad_et, PyTuple_New(0)));
    // End of approach 1

    // Approach 2
    // Use the bp::object to create a new instance - segfaults
    PyObject * bad_e(bp::incref(bp::object(f).ptr()));
    // End of approach 2

    // Raise the exception
    PyErr_SetObject((PyObject*)bad_et, bad_e);
}

Approach 1:
try:
    cpp_raise() - raises Foo
except Foo, e: - works
    print dir(e) - works
    print e.message - works, this data member is from the BaseException
    print e.msg - fails (see below), this is the msg data member we exposed

Boost.Python.ArgumentError: Python argument types in
    None.None(Foo)
did not match C++ signature:
    None(Foo {lvalue})
^^ Seems boost.python doesn't like Foo instances created this way

Approach 2:
try:
    cpp_raise() - raises Foo
except Foo, e: - works
    print e.msg - works, this is the msg data member we exposed
    print dir(e) - segfault
    print e.message - segfault
GC SEGFAULT

All the segfaults are caused by the fact that make_instance_impl 
(object/make_instance.hpp) is not making a "real" instance of the type, it is 
only calling type->tp_alloc, which doesn't run all the functions 
(BaseException_* in Objects/exception.c in Python source) necessary to make a 
proper Python exception object and leaves it with uninitialized data.
I suppose make_instance is working like that b/c it wasn't designed to create 
instances of types derived from native Python classes, but I can only guess 
here.

To sum up, it seems that if we manage to take the working parts from both 
approaches we get what we want, but it may not be as simple as it sounds and 
that's why I am asking for hints/comments/suggestions/solutions...

-- 
Best Regards,
Piotr Jaroszyński



More information about the Cplusplus-sig mailing list