Skip to content

Commit 8213257

Browse files
committed
Merge pull request #239 from johnhaddon/improvedWrappers
Improved Python Wrapping
2 parents 0e109f4 + 938532b commit 8213257

File tree

10 files changed

+530
-6
lines changed

10 files changed

+530
-6
lines changed

include/IECorePython/RefCountedBinding.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@
3636
#define IECOREPYTHON_REFCOUNTEDBINDING_H
3737

3838
#include "IECore/IntrusivePtr.h"
39+
#include "IECorePython/WrapperGarbageCollector.h"
3940

4041
namespace IECorePython
4142
{
43+
4244
void bindRefCounted();
4345

46+
/// \todo This class is now just an internal implementation detail of
47+
/// RefCountedClass - remove it from the public API for the next major version.
4448
template<typename T>
4549
struct IntrusivePtrToPython
4650
{
@@ -51,6 +55,8 @@ struct IntrusivePtrToPython
5155

5256
};
5357

58+
/// \todo This class is now just an internal implementation detail of
59+
/// RefCountedClass - remove it from the public API for the next major version.
5460
template<typename T>
5561
struct IntrusivePtrFromPython
5662
{
@@ -62,6 +68,41 @@ struct IntrusivePtrFromPython
6268

6369
};
6470

71+
/// A class similar to boost::python::wrapper, but with specialisations
72+
/// making it more suitable for use wrapping RefCounted types. See
73+
/// RunTimeTypedWrapper for a good example of its use.
74+
template<typename T>
75+
class RefCountedWrapper : public T, public WrapperGarbageCollector
76+
{
77+
78+
public :
79+
80+
RefCountedWrapper( PyObject *self );
81+
82+
template<typename Arg1>
83+
RefCountedWrapper( PyObject *self, Arg1 arg1 );
84+
85+
template<typename Arg1, typename Arg2>
86+
RefCountedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2 );
87+
88+
template<typename Arg1, typename Arg2, typename Arg3>
89+
RefCountedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2, Arg3 arg3 );
90+
91+
virtual ~RefCountedWrapper();
92+
93+
protected :
94+
95+
/// You must hold the GIL before calling this method, and should
96+
/// first have used isSubclassed() to check that it is worth trying.
97+
boost::python::object methodOverride( const char *name ) const;
98+
99+
private :
100+
101+
// Returns the Python type this class is bound as.
102+
static PyTypeObject *pyType();
103+
104+
};
105+
65106
/// A class to simplify the binding of RefCounted derived classes - this should be used in place of the usual
66107
/// boost::python::class_. It deals with many issues relating to intrusive pointers and object identity.
67108
template<typename T, typename Base, typename Ptr=IECore::IntrusivePtr<T> >

include/IECorePython/RefCountedBinding.inl

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@
4040
namespace IECorePython
4141
{
4242

43+
//////////////////////////////////////////////////////////////////////////
44+
// IntrusivePtr To/From Python
45+
//////////////////////////////////////////////////////////////////////////
46+
4347
template<typename T>
4448
IntrusivePtrToPython<T>::IntrusivePtrToPython()
4549
{
@@ -111,6 +115,62 @@ void IntrusivePtrFromPython<T>::construct( PyObject *source, boost::python::conv
111115
data->convertible = storage;
112116
}
113117

118+
//////////////////////////////////////////////////////////////////////////
119+
// RefCountedWrapper
120+
//////////////////////////////////////////////////////////////////////////
121+
122+
template<typename T>
123+
RefCountedWrapper<T>::RefCountedWrapper( PyObject *self )
124+
: T(), WrapperGarbageCollector( self, this, pyType() )
125+
{
126+
}
127+
128+
template<typename T>
129+
template<typename Arg1>
130+
RefCountedWrapper<T>::RefCountedWrapper( PyObject *self, Arg1 arg1 )
131+
: T( arg1 ), WrapperGarbageCollector( self, this, pyType() )
132+
{
133+
}
134+
135+
template<typename T>
136+
template<typename Arg1, typename Arg2>
137+
RefCountedWrapper<T>::RefCountedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2 )
138+
: T( arg1, arg2 ), WrapperGarbageCollector( self, this, pyType() )
139+
{
140+
}
141+
142+
template<typename T>
143+
template<typename Arg1, typename Arg2, typename Arg3>
144+
RefCountedWrapper<T>::RefCountedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2, Arg3 arg3 )
145+
: T( arg1, arg2, arg3 ), WrapperGarbageCollector( self, this, pyType() )
146+
{
147+
}
148+
149+
template<typename T>
150+
RefCountedWrapper<T>::~RefCountedWrapper()
151+
{
152+
}
153+
154+
/// You must hold the GIL before calling this method, and should
155+
/// first have used isSubclassed() to check that it is worth trying.
156+
template<typename T>
157+
boost::python::object RefCountedWrapper<T>::methodOverride( const char *name ) const
158+
{
159+
return WrapperGarbageCollector::methodOverride( name, pyType() );
160+
}
161+
162+
template<typename T>
163+
PyTypeObject *RefCountedWrapper<T>::pyType()
164+
{
165+
boost::python::converter::registration const &r
166+
= boost::python::converter::registered<T>::converters;
167+
return r.get_class_object();
168+
}
169+
170+
//////////////////////////////////////////////////////////////////////////
171+
// RefCountedClass
172+
//////////////////////////////////////////////////////////////////////////
173+
114174
template<typename T, typename Base, typename Ptr>
115175
RefCountedClass<T, Base, Ptr>::RefCountedClass( const char *className, const char *docString )
116176
: BaseClass( className, docString, boost::python::no_init )

include/IECorePython/RunTimeTypedBinding.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#ifndef IECOREPYTHON_RUNTIMETYPEDBINDING_H
3737
#define IECOREPYTHON_RUNTIMETYPEDBINDING_H
3838

39+
#include "IECore/RunTimeTyped.h"
40+
3941
#include "IECorePython/RefCountedBinding.h"
4042
#include "IECorePython/ScopedGILLock.h"
4143

@@ -44,6 +46,33 @@ namespace IECorePython
4446

4547
void bindRunTimeTyped();
4648

49+
/// A class for wrapping RunTimeTyped objects to allow overriding
50+
/// in Python. It automatically forwards all RunTimeTyped virtual
51+
/// functions to Python overrides if they exist.
52+
template<typename T>
53+
class RunTimeTypedWrapper : public RefCountedWrapper<T>
54+
{
55+
56+
public :
57+
58+
RunTimeTypedWrapper( PyObject *self );
59+
60+
template<typename Arg1>
61+
RunTimeTypedWrapper( PyObject *self, Arg1 arg1 );
62+
63+
template<typename Arg1, typename Arg2>
64+
RunTimeTypedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2 );
65+
66+
template<typename Arg1, typename Arg2, typename Arg3>
67+
RunTimeTypedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2, Arg3 arg3 );
68+
69+
virtual IECore::TypeId typeId() const;
70+
virtual const char *typeName() const;
71+
virtual bool isInstanceOf( IECore::TypeId typeId ) const;
72+
virtual bool isInstanceOf( const char *typeName ) const;
73+
74+
};
75+
4776
/// A class to simplify the binding of RunTimeTyped derived classes. This should be used
4877
/// in place of the usual boost::python::class_. It automatically makes sure the class is bound
4978
/// with the correct name and base class, as well as dealing with all the issues that RefCountedClass
@@ -60,6 +89,8 @@ class RunTimeTypedClass : public RefCountedClass<T, typename T::BaseClass, Ptr>
6089

6190
};
6291

92+
/// \deprecated
93+
/// \todo Remove for the next major version.
6394
#define IECOREPYTHON_RUNTIMETYPEDWRAPPERFNS( CLASSNAME )\
6495
virtual IECore::TypeId typeId() const\
6596
{\

include/IECorePython/RunTimeTypedBinding.inl

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,111 @@ static bool isInstanceOf2( T &t, const char *n )
7272

7373
} // namespace Detail
7474

75+
//////////////////////////////////////////////////////////////////////////
76+
// RunTimeTypedWrapper
77+
//////////////////////////////////////////////////////////////////////////
78+
79+
template<typename T>
80+
RunTimeTypedWrapper<T>::RunTimeTypedWrapper( PyObject *self )
81+
: RefCountedWrapper<T>( self )
82+
{
83+
}
84+
85+
template<typename T>
86+
template<typename Arg1>
87+
RunTimeTypedWrapper<T>::RunTimeTypedWrapper( PyObject *self, Arg1 arg1 )
88+
: RefCountedWrapper<T>( self, arg1 )
89+
{
90+
}
91+
92+
template<typename T>
93+
template<typename Arg1, typename Arg2>
94+
RunTimeTypedWrapper<T>::RunTimeTypedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2 )
95+
: RefCountedWrapper<T>( self, arg1, arg2 )
96+
{
97+
}
98+
99+
template<typename T>
100+
template<typename Arg1, typename Arg2, typename Arg3>
101+
RunTimeTypedWrapper<T>::RunTimeTypedWrapper( PyObject *self, Arg1 arg1, Arg2 arg2, Arg3 arg3 )
102+
: RefCountedWrapper<T>( self, arg1, arg2, arg3 )
103+
{
104+
}
105+
106+
template<typename T>
107+
IECore::TypeId RunTimeTypedWrapper<T>::typeId() const
108+
{
109+
if( this->isSubclassed() )
110+
{
111+
IECorePython::ScopedGILLock gilLock;
112+
if( boost::python::object f = this->methodOverride( "typeId" ) )
113+
{
114+
boost::python::object res = f();
115+
return boost::python::extract<IECore::TypeId>( res );
116+
}
117+
}
118+
return T::typeId();
119+
}
120+
121+
template<typename T>
122+
const char *RunTimeTypedWrapper<T>::typeName() const
123+
{
124+
if( this->isSubclassed() )
125+
{
126+
IECorePython::ScopedGILLock gilLock;
127+
if( boost::python::object f = this->methodOverride( "typeName" ) )
128+
{
129+
boost::python::object res = f();
130+
return boost::python::extract<const char *>( res );
131+
}
132+
}
133+
return T::typeName();
134+
}
135+
136+
template<typename T>
137+
bool RunTimeTypedWrapper<T>::isInstanceOf( IECore::TypeId typeId ) const
138+
{
139+
if( T::isInstanceOf( typeId ) )
140+
{
141+
return true;
142+
}
143+
144+
if( this->isSubclassed() )
145+
{
146+
IECorePython::ScopedGILLock gilLock;
147+
if( boost::python::object f = this->methodOverride( "isInstanceOf" ) )
148+
{
149+
boost::python::object res = f( typeId );
150+
return boost::python::extract<bool>( res );
151+
}
152+
}
153+
return false;
154+
}
155+
156+
template<typename T>
157+
bool RunTimeTypedWrapper<T>::isInstanceOf( const char *typeName ) const
158+
{
159+
if( T::isInstanceOf( typeName ) )
160+
{
161+
return true;
162+
}
163+
164+
if( this->isSubclassed() )
165+
{
166+
IECorePython::ScopedGILLock gilLock;
167+
if( boost::python::object f = this->methodOverride( "isInstanceOf" ) )
168+
{
169+
boost::python::object res = f( typeName );
170+
return boost::python::extract<bool>( res );
171+
}
172+
}
173+
return false;
174+
}
175+
176+
//////////////////////////////////////////////////////////////////////////
177+
// RunTimeTypedClass
178+
//////////////////////////////////////////////////////////////////////////
179+
75180
template<typename T, typename Ptr>
76181
RunTimeTypedClass<T, Ptr>::RunTimeTypedClass( const char *docString )
77182
: BaseClass( Detail::nameWithoutNamespace( T::staticTypeName() ), docString )

include/IECorePython/Wrapper.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,9 @@ namespace IECorePython
4848
/// for this alone because it has massive issues with memory management - ie it
4949
/// makes no attempt to do any, whereas our WrapperGarbageCollector base class
5050
/// handles that in ok fashion.
51-
/// \todo I suspect we can completely drop the inheritance
52-
/// of boost::python::wrapper as we only use it for the get_override method, and
53-
/// we could copy it out of the boost sources. Doing so would make the rather
54-
/// complicated class hierarchy here a lot simpler.
51+
/// \deprecated The RefCountedWrapper and RunTimeTypedWrapper classes provide
52+
/// a replacement for this class.
53+
/// \todo Remove for the next major version.
5554
template<typename T>
5655
class Wrapper : public boost::python::wrapper<T>, public WrapperGarbageCollector
5756
{

include/IECorePython/WrapperGarbageCollector.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ class WrapperGarbageCollector
5656

5757
public :
5858

59+
/// \deprecated
60+
/// \todo Remove for major version 9.
5961
WrapperGarbageCollector( PyObject *pyObject, IECore::RefCounted *object );
6062
virtual ~WrapperGarbageCollector();
6163

@@ -82,7 +84,46 @@ class WrapperGarbageCollector
8284

8385
protected :
8486

87+
/// Constructor for use by RefCountedWrapper derived class. The self argument is
88+
/// the python object which holds the wrapped RefCounted argument, and wrappedType
89+
/// is the Python type corresponding to the C++ wrapper. This constructor
90+
/// initialises m_pyObject as follows :
91+
///
92+
/// If the python type of self is not wrappedType, this indicates that self
93+
/// is an instance of a python subclass of the wrapped C++ class. In this case
94+
/// we want to support access to virtual overrides via methodOverride(). Therefore
95+
/// m_pyObject is initialised to point to self, and self's reference count is
96+
/// incremented to keep it alive as long as the C++ object is alive. This reference
97+
/// cycle will be broken by the collect() method. After this, isSubclassed() will
98+
/// return true and methodOverride() may be used from virtual overrides to call into
99+
/// python.
100+
///
101+
/// If the python type of self is the same as wrappedType, this indicates that self
102+
/// is not subclassed in python. In this case we don't want to support virtual overrides
103+
/// nor do we want to pay the overhead of garbage collection. Therefore m_pyObject
104+
/// is initialised to NULL, isSubclassed() will return false, methodOverride() will always
105+
/// fail, and we will have no further overhead.
106+
WrapperGarbageCollector( PyObject *self, IECore::RefCounted *wrapped, PyTypeObject *wrappedType );
107+
108+
/// Returns true if this instance is of a Python subclass, and methodOverride() is therefore
109+
/// useable. This method may be called without holding the GIL, so it should be tested first
110+
/// and methodOverride() only called if it returns true - this avoids the huge overhead associated
111+
/// with acquiring the GIL and entering Python only to discover that there is no override.
112+
bool isSubclassed() const
113+
{
114+
return m_pyObject;
115+
}
116+
117+
/// Returns an overridden method for this instance if one exists. The GIL must
118+
/// be held before calling this method. See isSubclassed() for an important means of
119+
/// avoiding this overhead where it isn't necessary. Also see RefCountedWrapper::methodOverride()
120+
/// which provides an overload which automatically supplies wrappedType.
121+
boost::python::object methodOverride( const char *name, PyTypeObject *wrappedType ) const;
122+
123+
/// \todo Make private for the next major version.
85124
PyObject *m_pyObject;
125+
/// \todo This is unused. Remove it for
126+
/// the next major version.
86127
IECore::RefCounted *m_object;
87128

88129
private :

0 commit comments

Comments
 (0)