[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