Reputation: 6338
I'm using Boost.Python 1.54 on Windows with MSVC2010, and I have a problem storing a pointer to one class in a second class from python, and retrieving it. It seems to change data type somehow.
Here's my classes:
typedef unsigned int uint_t;
struct classA {
int intval;
unsigned int bitfield_member:1;
};
struct Collection {
classA * class_a_ptr;
};
and here's how I expose them to python (some of this code was originally autogenerated by Py++, but I've hand-edited it since then):
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/return_value_policy.hpp>
#include <boost/python/manage_new_object.hpp>
namespace bp = boost::python;
struct Collection_wrapper : Collection, bp::wrapper< Collection > {
Collection_wrapper(Collection const & arg )
: Collection( arg )
, bp::wrapper< Collection >(){
// copy constructor
}
Collection_wrapper()
: Collection()
, bp::wrapper< Collection >(){
// null constructor
}
static ::classA * get_class_a_ptr(Collection const & inst ){
return inst.class_a_ptr;
}
static void set_class_a_ptr( Collection & inst, ::classA * new_value ){
inst.class_a_ptr = new_value;
}
};
struct classA_wrapper : classA, bp::wrapper< classA > {
classA_wrapper(classA const & arg )
: classA( arg )
, bp::wrapper< classA >(){
// copy constructor
}
classA_wrapper()
: classA()
, bp::wrapper< classA >(){
// null constructor
}
::uint_t get_bitfield_member() const {
return bitfield_member;
}
void set_bitfield_member( ::uint_t new_value ){
bitfield_member = new_value;
}
};
BOOST_PYTHON_MODULE(render_lib_ext)
{
using namespace bp;
{ //::Collection
typedef bp::class_< Collection_wrapper > Collection_exposer_t;
Collection_exposer_t Collection_exposer = Collection_exposer_t( "Collection" );
bp::scope Collection_scope( Collection_exposer );
// original version, fails
Collection_exposer.add_property( "class_a_ptr"
, bp::make_function( (::classA * (*)( ::Collection const & ))(&Collection_wrapper::get_class_a_ptr), bp::return_internal_reference< >() )
, bp::make_function( (void (*)( ::Collection &,::classA * ))(&Collection_wrapper::set_class_a_ptr), bp::with_custodian_and_ward_postcall< 1, 2 >() ) );
}
{ //::classA
typedef bp::class_< classA_wrapper > classA_exposer_t;
classA_exposer_t classA_exposer = classA_exposer_t( "classA" );
bp::scope classA_scope( classA_exposer );
classA_exposer.def_readwrite( "intval", &classA::intval );
classA_exposer.add_property( "bitfield_member"
, (::uint_t ( classA_wrapper::* )( ) const)(&classA_wrapper::get_bitfield_member)
, (void ( classA_wrapper::* )( ::uint_t ) )(&classA_wrapper::set_bitfield_member) );
}
}
and here's the python test to exercise it:
import unittest
import render_lib_ext as RL
class TestRenderLib(unittest.TestCase):
def test_globals(self):
coll=RL.Collection()
g = RL.classA()
g.intval=9801;
self.assertEqual(9801, g.intval)
coll.class_a_ptr = g # store pointer in collection
geg = coll.class_a_ptr # retrieve it
self.assertEqual(0, g.bitfield_member) # works
self.assertEqual(0, geg.bitfield_member) # fails with ArgumentError (type error)
self.assertEqual(9801, geg.intval) # fails! Is it not the same object?
It fails with this error on the first "fails" line:
Traceback (most recent call last):
File "test2.py", line 18, in test_globals
self.assertEqual(0, geg.bitfield_member) # fails with ArgumentError (type error)
ArgumentError: Python argument types in
None.None(classA)
did not match C++ signature:
None(struct classA_wrapper {lvalue})
which seems odd to me since classA_wrapper extends classA. What am I doing wrong? Is there a different way to do this? I'm pretty experienced in python and c++, but this is my first foray into Boost.Python.
Upvotes: 1
Views: 319
Reputation: 51871
The functors exposed to the bitfield_member
property on classA
needs to explicitly accept the instance on which they operate. It is equivalent to the property()
method in Python, where fget and fset accept the self
argument. Therefore, change the bitfield_member
getter and setter functions to be static and accept classA&
as their first argument.
// ...
struct classA_wrapper: ...
{
// ...
static ::uint_t get_bitfield_member(classA& self)
{
return self.bitfield_member;
}
static void set_bitfield_member(classA& self, ::uint_t new_value)
{
self.bitfield_member = new_value;
}
};
BOOST_PYTHON_MODULE(...)
{
namespace python = boost::python;
// ...
python::class_< classA_wrapper >("classA")
.def_readwrite("intval", &classA::intval)
.add_property("bitfield_member",
&classA_wrapper::get_bitfield_member,
&classA_wrapper::set_bitfield_member)
;
}
}
Although get_bitfield_member
and set_bitfield_member
are member functions in the original code, the Python classA
object returned from class_a_ptr
does not appear to have been completely initialized its underlying C++ type. This may be the result of undefined behavior within the Boost.Python API.
The problem does not surface elsewhere, because:
Collection.class_a_ptr
property's fget and fset explicitly accepts the instance argument.classA.intval
property uses def_readwrite
, which will implicitly create fget and fset that accepts the instance via make_getter/make_setter
.Here is a complete example based on the original code:
#include <boost/python.hpp>
typedef unsigned int uint_t;
struct classA
{
int intval;
unsigned int bitfield_member:1;
};
struct Collection
{
classA * class_a_ptr;
};
namespace python = boost::python;
struct Collection_wrapper
: Collection, python::wrapper<Collection>
{
Collection_wrapper() {}
Collection_wrapper(const Collection& self)
: Collection(self)
{}
static ::classA* get_class_a_ptr(const Collection& self)
{
return self.class_a_ptr;
}
static void set_class_a_ptr(Collection& self, ::classA * new_value)
{
self.class_a_ptr = new_value;
}
};
struct classA_wrapper
: classA, python::wrapper<classA>
{
classA_wrapper() {}
classA_wrapper(const classA& self)
: classA(self)
{}
static ::uint_t get_bitfield_member(const classA& self)
{
return self.bitfield_member;
}
static void set_bitfield_member(classA& self, ::uint_t new_value)
{
self.bitfield_member = new_value;
}
};
BOOST_PYTHON_MODULE(example)
{
python::class_<Collection_wrapper>("Collection")
.add_property("class_a_ptr",
python::make_function(&Collection_wrapper::get_class_a_ptr,
python::return_internal_reference<>()),
python::make_function(&Collection_wrapper::set_class_a_ptr,
python::with_custodian_and_ward_postcall<1, 2>()))
;
python::class_<classA_wrapper>("classA")
.def_readwrite("intval", &classA::intval)
.add_property("bitfield_member",
&classA_wrapper::get_bitfield_member,
&classA_wrapper::set_bitfield_member)
;
}
And its usage:
>>> import example
>>> collection = example.Collection()
>>> a = example.classA()
>>> a.intval = 9801
>>> print a.intval
9801
>>> collection.class_a_ptr = a
>>> same_a = collection.class_a_ptr
>>> a.bitfield_member = 0
>>> print a.bitfield_member
0
>>> print same_a.bitfield_member
0
>>> same_a.bitfield_member = 1
>>> print a.bitfield_member
1
>>> print same_a.bitfield_member
1
Upvotes: 1