[C++-sig] Runtime issues with return_internal_referenceand to_python_converter
Mark English
markenglish at freenet.co.uk
Fri Aug 3 00:12:08 CEST 2007
English, Mark <Mark.English <at> rbccm.com> writes:
> In short I've registered a "to_python_converter" for a C++ type, and it's
not being found with
> "return_internal_reference" although it is used with "return_by_value".
A potential solution.
Essentially I ended up cut'n'pasting return_internal_reference and it's
surrounding code to call a customised "MakeHolder" model
which looks up the C++ type in the converter registry.
Since the type ends up in a with_custodian_and_ward_postcall it needs to
support weakref, so in the example PyInner got a bit more convoluted too.
http://docs.python.org/ext/weakref-support.html
I'm sure there are better ways to do this, and I am concerned that
my lack of boost python understanding and mpl inexperience may lead to bugs
especially with PyObject ref counting,
so better approaches or flaws in this approach gratefully received.
Regards,
Mark
========== Code ==========
---- custom_return_internal_reference.hpp ----
// File: custom_return_internal_reference.hpp
// Description:
/// custom_call_policies::return_registered_internal_reference
/// Customised version of return_internal_reference which allows delegation
/// to some externally defined "MakeHolder" class
/// (defined as "A class whose static execute() creates an instance_holder")
#pragma once
# include <boost/python/default_call_policies.hpp>
# include <boost/python/return_internal_reference.hpp>
# include <boost/python/reference_existing_object.hpp>
# include <boost/python/to_python_indirect.hpp>
# include <boost/mpl/if.hpp>
# include <boost/type_traits/is_same.hpp>
namespace custom_call_policies
{
namespace detail
{
using namespace boost::python;
/// Default "MakeHolder" model based on
/// "boost/python/to_python_indirect.hpp - detail::make_reference_holder" and
/// "boost/python/to_python_value.hpp - detail::registry_to_python_value"
struct make_registered_reference_holder
{
/// Turns a pointer to a C++ type "T" into a "PyObject *" using registered
/// type lookup. This means C++ type must be manually registered for
/// conversion
/// @param T Parameterised C++ type to convert
/// @param p Pointer to instance of C++ type to convert
/// @return Python object built from registered conversion code
template <class T>
static PyObject* execute(T* p)
{
typedef objects::pointer_holder<T*, T> holder_t;
T* q = const_cast<T*>(p);
// Jump into conversion lookup mechanism
typedef T argument_type;
typedef converter::registered<argument_type> r;
# if BOOST_WORKAROUND(__GNUC__, < 3)
// suppresses an ICE, somehow
(void)r::converters;
# endif
return converter::registered<argument_type>::converters.to_python(q);
}
};
/// reference_existing_object replacement allowing use of different
/// "MakeHolder" model.
/// @param MakeReferenceHolderSubstitute - Class modelling "MakeHolder"
/// Defaults to
/// make_registered_reference_holder
template <class MakeReferenceHolderSubstitute>
struct subst_reference_existing_object :
boost::python::reference_existing_object
{
/// Implicitly relies on "detail" namespace implementation, and falls back
/// on that implementation if it changes
template <class T>
struct apply
{
private:
typedef typename reference_existing_object::apply<T>::type basetype_;
public:
typedef typename boost::mpl::if_<
boost::is_same<basetype_
,boost::python::to_python_indirect<
T, boost::python::detail::make_reference_holder> >
,boost::python::to_python_indirect<T, MakeReferenceHolderSubstitute>
,basetype_
>::type type;
};
};
/// return_internal_reference replacement allowing use of different
/// "ResultConverterGenerator" model rather than "reference_existing_object"
/// Falls back on Boost implementation if it ceases to use
/// "reference_existing_object"
/// @param ReferenceExistingObjectSubstitute - "ResultConverterGenerator"
/// model replacement for
/// "reference_existing_object"
/// @param owner_arg - See boost documentation
/// @param BasePolicy_ - See boost documentation
template <class ReferenceExistingObjectSubstitute
,std::size_t owner_arg = 1, class BasePolicy_ = default_call_policies>
struct subst_return_internal_reference :
boost::python::return_internal_reference<owner_arg, BasePolicy_>
{
private:
typedef boost::python::return_internal_reference<owner_arg
,BasePolicy_> basetype_;
public:
typedef typename boost::mpl::if_<
boost::is_same<typename basetype_::result_converter
,boost::python::reference_existing_object>
,ReferenceExistingObjectSubstitute
,typename basetype_::result_converter>::type result_converter;
};
} // Ends namespace detail
// Typedefs for programmer convenience
typedef detail::subst_reference_existing_object<
detail::make_registered_reference_holder
> reference_registered_existing_object;
// In place of a typedef template
/// Call policy to create internal references to registered types
template <std::size_t owner_arg = 1, class BasePolicy_ = default_call_policies>
struct return_registered_internal_reference :
detail::subst_return_internal_reference<reference_registered_existing_object
,owner_arg, BasePolicy_>
{};
} // Ends namespace custom_call_policies
---- test.cpp ----
#include <boost/python.hpp>
#include <structmember.h> // PyMemberDef
#include "custom_return_internal_reference.hpp"
class Inner
{};
struct PyInner
{
PyObject_HEAD
PyObject *ppyobjWeakrefList;
Inner *pinner;
};
// New/Dealloc added to support weakref
PyObject * PyInnerNew (PyTypeObject *ptypeNew, PyObject *ppyobjArgs, PyObject
*ppyobjKwds)
{
PyObject *ppyobjNew = PyType_GenericNew(ptypeNew, ppyobjArgs, ppyobjKwds);
if (ppyobjNew != NULL && !PyErr_Occurred()) // If created object ok
{
PyInner *ppyinnerNew = reinterpret_cast<PyInner *>(ppyobjNew);
ppyinnerNew->ppyobjWeakrefList = NULL;
}
return ppyobjNew;
}
static void PyInnerDealloc(PyInner *ppyinnerDealloc)
{
// Allocate temporaries if needed, but do not begin destruction just yet
if (ppyinnerDealloc->ppyobjWeakrefList != NULL)
{
PyObject_ClearWeakRefs(reinterpret_cast<PyObject *>(ppyinnerDealloc));
}
ppyinnerDealloc->ob_type->tp_free(ppyinnerDealloc);
}
static PyMemberDef l_amemberPyInner[] =
{
{"__weakref__", T_OBJECT, offsetof(PyInner, ppyobjWeakrefList), 0},
{0}
};
PyTypeObject typeInner =
{
PyObject_HEAD_INIT(NULL)
0 // ob_size (?!?!?)
,"Inner" // tp_name
,sizeof(PyInner) // tp_basicsize
,0 // tp_itemsize
,(destructor)&PyInnerDealloc // tp_dealloc
,0 // tp_print
,0 // tp_getattr
,0 // tp_setattr
,0 // tp_compare
,0 // tp_repr
,0 // tp_as_number
,0 // tp_as_sequence
,0 // tp_as_mapping
,0 // tp_hash
,0 // tp_call
,0 // tp_str
,0 // tp_getattro
,0 // tp_setattro
,0 // tp_as_buffer
,Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE // tp_flags
|Py_TPFLAGS_HAVE_WEAKREFS
,0 // tp_doc
,0 // tp_traverse
,0 // tp_clear
,0 // tp_richcompare
,offsetof(PyInner, ppyobjWeakrefList) // tp_weaklistoffset
,0 // tp_iter
,0 // tp_iternext
,0 // tp_methods
,l_amemberPyInner // tp_members
,0 // tp_getset
,0 // tp_base
,0 // tp_dict
,0 // tp_descr_get
,0 // tp_descr_set
,0 // tp_dictoffset
,0 // tp_init
,0 // tp_alloc
,PyInnerNew // tp_new
};
struct convert_inner_to_python
{
static PyObject * convert(Inner const &rinner)
{
PyInner *ppyinnerReturn = PyObject_New(PyInner, &typeInner);
ppyinnerReturn->ppyobjWeakrefList = NULL;
ppyinnerReturn->pinner = const_cast<Inner *>(&rinner); // This looks
decidedly like a bad idea
PyObject *ppyobjReturn = reinterpret_cast <PyObject *>(ppyinnerReturn);
return ppyobjReturn;
}
};
struct Outer
{
Inner m_inner;
};
namespace ConversionExample
{
void export()
{
using namespace boost::python;
using custom_call_policies::return_registered_internal_reference;
typeInner.ob_type = &PyType_Type;
PyType_Ready(&typeInner);
to_python_converter<Inner, convert_inner_to_python>();
// convert_python_to_inner();
class_<Outer>("Outer")
// This works but object lifetime is incorrect
.add_property("innerval", make_getter(&Outer::m_inner,
return_value_policy<return_by_value>()),
make_setter(&Outer::m_inner,
return_value_policy<return_by_value>()))
//This now also works
.add_property("inner", make_getter(&Outer::m_inner,
return_registered_internal_reference<>()),
make_setter(&Outer::m_inner,
return_registered_internal_reference<>()))
;
}
} // End of namespace ConversionExample
namespace {
BOOST_PYTHON_MODULE("testboostpython")
{
ConversionExample::export();
}
} // Ends anonymous namespace
---- test.py ----
import unittest
class TestCaseBoostPython(unittest.TestCase):
def test_OuterInner(self):
import testboostpython
import gc
def do_test_value():
outerTest = testboostpython.Outer()
inner = outerTest.innerval
del outerTest
return inner
return inner
def do_test():
outerTest = testboostpython.Outer()
inner = outerTest.inner
del outerTest
return inner
#This works but wrong lifetime
innerTestVal = do_test_value()
gc.collect()
self.assertEqual(type(innerTestVal).__name__, 'Inner')
#This also works
innerTest = do_test()
gc.collect()
self.assertEqual(type(innerTest).__name__, 'Inner')
if __name__ == "__main__":
from test.test_support import run_unittest
run_unittest(TestCaseBoostPython)
========== Output ==========
test_OuterInner (__main__.TestCaseBoostPython) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.015s
OK
More information about the Cplusplus-sig
mailing list