[C++-sig] Runtime issues with return_internal_referenceand to_python_converter

Mark English markenglish at freenet.co.uk
Sat Aug 4 16:34:18 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".
(Reposted because I messed up the subject header before)

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