Skip to content

Commit d577ccf

Browse files
author
florianlink
committed
added initial support for passing ownership to C++ and Python using templates as markup
git-svn-id: http://svn.code.sf.net/p/pythonqt/code/trunk@375 ea8d5007-eb21-0410-b261-ccb3ea6e24a9
1 parent 6f2c21f commit d577ccf

12 files changed

+309
-44
lines changed

src/PythonQt.cpp

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ void PythonQt::init(int flags, const QByteArray& pythonQtModuleName)
238238
Py_INCREF(obj);
239239
PyModule_AddObject(pack2, enumNames[i], obj);
240240
}
241+
242+
_self->priv()->pythonQtModule().addObject("Debug", _self->priv()->_debugAPI);
241243
}
242244
}
243245

@@ -314,7 +316,6 @@ PythonQt::PythonQt(int flags, const QByteArray& pythonQtModuleName)
314316
Py_INCREF(&PythonQtStdInRedirectType);
315317

316318
initPythonQtModule(flags & RedirectStdOut, pythonQtModuleName);
317-
318319
}
319320

320321
PythonQt::~PythonQt() {
@@ -1290,6 +1291,7 @@ PythonQtPrivate::PythonQtPrivate()
12901291
_profilingCB = NULL;
12911292
_hadError = false;
12921293
_systemExitExceptionHandlerEnabled = false;
1294+
_debugAPI = new PythonQtDebugAPI(this);
12931295
}
12941296

12951297
void PythonQtPrivate::setupSharedLibrarySuffixes()
@@ -2013,16 +2015,26 @@ QString PythonQtPrivate::getSignature(PyObject* object)
20132015
void PythonQtPrivate::shellClassDeleted( void* shellClass )
20142016
{
20152017
PythonQtInstanceWrapper* wrap = _wrappedObjects.value(shellClass);
2016-
if (wrap && wrap->_wrappedPtr) {
2017-
// this is a pure C++ wrapper and the shell has gone, so we need
2018-
// to set the _wrappedPtr to NULL on the wrapper
2019-
wrap->_wrappedPtr = NULL;
2020-
// and then we remove the wrapper, since the wrapped class is gone
2021-
_wrappedObjects.remove(shellClass);
2022-
}
2023-
// if the wrapper is a QObject, we do not handle this here,
2024-
// it will be handled by the QPointer<> to the QObject, which becomes NULL
2025-
// via the QObject destructor.
2018+
if (wrap) {
2019+
if (wrap->_wrappedPtr) {
2020+
// this is a pure C++ wrapper and the shell has gone, so we need
2021+
// to set the _wrappedPtr to NULL on the wrapper
2022+
wrap->_wrappedPtr = NULL;
2023+
// and then we remove the wrapper, since the wrapped class is gone
2024+
_wrappedObjects.remove(shellClass);
2025+
}
2026+
// if the wrapper is a QObject, we do not handle this here,
2027+
// it will be handled by the QPointer<> to the QObject, which becomes NULL
2028+
// via the QObject destructor.
2029+
2030+
// if the shell was owned by C++ and is deleted via C++, we
2031+
// need to decrement the ref-count of the wrapper so that is can
2032+
// be released.
2033+
if (wrap->_shellInstanceRefCountsWrapper) {
2034+
Py_DECREF((PyObject*)wrap);
2035+
wrap->_shellInstanceRefCountsWrapper = false;
2036+
}
2037+
}
20262038
}
20272039

20282040
PyObject* PythonQtPrivate::wrapMemoryAsBuffer( const void* data, Py_ssize_t size )

src/PythonQt.h

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,58 @@ template<class T> void PythonQtSetInstanceWrapperOnShell(void* object, PythonQtI
7979
(reinterpret_cast<T*>(object))->_wrapper = wrapper;
8080
}
8181

82+
//! Helper template that allows to pass the ownership of a C++ instance between C++ and Python
83+
//! (it is used as a slot return type or parameter type so that it can be detected by the PythonQt
84+
//! slot calling code).
85+
template<class T>
86+
class PythonQtPassOwnershipToCPP
87+
{
88+
public:
89+
//! Allow conversion from T to PythonQtPassOwnershipToCPP<T>
90+
PythonQtPassOwnershipToCPP(const T& t):_t(t) {}
91+
//! Allow conversion from PythonQtPassOwnershipToCPP<T> to T
92+
operator T() const { return _t; }
93+
94+
//! Stored value. This is important so that it has the same memory layout
95+
//! as a pointer if T is a pointer type (which is the typical use case for this class).
96+
T _t;
97+
};
98+
99+
//! Helper template that allows to pass the ownership of a C++ instance between C++ and Python
100+
//! (it is used as a slot return type or parameter type so that it can be detected by the PythonQt
101+
//! slot calling code).
102+
template<class T>
103+
class PythonQtPassOwnershipToPython
104+
{
105+
public:
106+
//! Allow conversion from T to PythonQtPassOwnershipToPython<T>
107+
PythonQtPassOwnershipToPython(const T& t):_t(t) {}
108+
//! Allow conversion from PythonQtPassOwnershipToPython<T> to T
109+
operator T() const { return _t; }
110+
111+
//! Stored value. This is important so that it has the same memory layout
112+
//! as a pointer if T is a pointer type (which is the typical use case for this class).
113+
T _t;
114+
};
115+
116+
//! Helper template that allows to pass the ownership of a C++ instance between C++ and Python
117+
//! (it is used as a slot return type or parameter type so that it can be detected by the PythonQt
118+
//! slot calling code).
119+
template<class T>
120+
class PythonQtNewOwnerOfThis
121+
{
122+
public:
123+
//! Allow conversion from T to PythonQtNewOwnerOfThis<T>
124+
PythonQtNewOwnerOfThis(const T& t):_t(t) {}
125+
//! Allow conversion from PythonQtNewOwnerOfThis<T> to T
126+
operator T() const { return _t; }
127+
128+
//! Stored value. This is important so that it has the same memory layout
129+
//! as a pointer if T is a pointer type (which is the typical use case for this class).
130+
T _t;
131+
};
132+
133+
82134
//! returns the offset that needs to be added to upcast an object of type T1 to T2
83135
template<class T1, class T2> int PythonQtUpcastingOffset() {
84136
return ((reinterpret_cast<char*>(static_cast<T2*>(reinterpret_cast<T1*>(0x100))))
@@ -554,6 +606,8 @@ class PYTHONQT_EXPORT PythonQt : public QObject {
554606

555607
};
556608

609+
class PythonQtDebugAPI;
610+
557611
//! internal PythonQt details
558612
class PYTHONQT_EXPORT PythonQtPrivate : public QObject {
559613

@@ -739,6 +793,8 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject {
739793

740794
PythonQt::ProfilingCB* _profilingCB;
741795

796+
PythonQtDebugAPI* _debugAPI;
797+
742798
int _initFlags;
743799
int _PythonQtObjectPtr_metaId;
744800

src/PythonQtClassWrapper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,9 @@ static PyObject *PythonQtClassWrapper_getattro(PyObject *obj, PyObject *name)
443443
return NULL;
444444
}
445445
if (obj == (PyObject*)&PythonQtInstanceWrapper_Type) {
446-
return NULL;
446+
// if we are called as PythonQtInstanceWrapper_Type, we need to get the properties from the type...
447+
PyObject* superAttr = PyType_Type.tp_getattro(obj, name);
448+
return superAttr;
447449
}
448450

449451
if (qstrcmp(attributeName, "__dict__")==0) {

src/PythonQtConversion.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,13 @@ void* PythonQtConv::ConvertPythonToQt(const PythonQtMethodInfo::ParameterInfo& i
391391
PythonQtInstanceWrapper* wrap = (PythonQtInstanceWrapper*)obj;
392392
void* object = castWrapperTo(wrap, info.name, ok);
393393
if (ok) {
394+
if (info.passOwnershipToCPP) {
395+
// Example: QLayout::addWidget(QWidget*)
396+
wrap->passOwnershipToCPP();
397+
} else if (info.passOwnershipToPython) {
398+
// Example: QLayout::removeWidget(QWidget*)
399+
wrap->passOwnershipToPython();
400+
}
394401
if (info.pointerCount==1) {
395402
// store the wrapped pointer in an extra pointer and let ptr point to the extra pointer
396403
PythonQtValueStorage_ADD_VALUE_IF_NEEDED(alreadyAllocatedCPPObject,global_ptrStorage, void*, object, ptr);

src/PythonQtInstanceWrapper.cpp

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ static PyObject* PythonQtInstanceWrapper_new(PyTypeObject *type, PyObject * /*ar
140140
self->_ownedByPythonQt = false;
141141
self->_useQMetaTypeDestroy = false;
142142
self->_isShellInstance = false;
143+
self->_shellInstanceRefCountsWrapper = false;
143144
}
144145
return (PyObject *)self;
145146
}
@@ -154,7 +155,8 @@ int PythonQtInstanceWrapper_init(PythonQtInstanceWrapper * self, PyObject * args
154155
// we are called from python, try to construct our object
155156
if (self->classInfo()->constructors()) {
156157
void* directCPPPointer = NULL;
157-
PythonQtSlotFunction_CallImpl(self->classInfo(), NULL, self->classInfo()->constructors(), args, kwds, NULL, &directCPPPointer);
158+
PythonQtPassThisOwnershipType ownership;
159+
PythonQtSlotFunction_CallImpl(self->classInfo(), NULL, self->classInfo()->constructors(), args, kwds, NULL, &directCPPPointer, &ownership);
158160
if (PyErr_Occurred()) {
159161
return -1;
160162
}
@@ -164,11 +166,11 @@ int PythonQtInstanceWrapper_init(PythonQtInstanceWrapper * self, PyObject * args
164166
if (refCB) {
165167
(*refCB)(directCPPPointer);
166168
}
167-
168-
// change ownershipflag to be owned by PythonQt
169+
// change ownership flag to be owned by PythonQt
169170
self->_ownedByPythonQt = true;
170171
self->_useQMetaTypeDestroy = false;
171-
if (self->classInfo()->isCPPWrapper()) {
172+
bool isQObject = !self->classInfo()->isCPPWrapper();
173+
if (!isQObject) {
172174
self->_wrappedPtr = directCPPPointer;
173175
// TODO xxx: if there is a wrapper factory, we might want to generate a wrapper for our class?!
174176
} else {
@@ -189,6 +191,16 @@ int PythonQtInstanceWrapper_init(PythonQtInstanceWrapper * self, PyObject * args
189191
self->_isShellInstance = true;
190192
}
191193
}
194+
// if the constructor has a PythonQtPassThisOwnership parameter and that owner is not NULL,
195+
// this wrapper is immediately owned by CPP
196+
// (Example: QGraphicsItem(QGraphicsItem* parent), if parent != NULL, ownership needs to be passed
197+
// to C++ immediately)
198+
// Alternatively, if it is a QObject and the object already has a parent when it is constructed,
199+
// the ownership should be moved to C++ as well, so that the shell instance stays alive.
200+
if (ownership == PassOwnershipToCPP ||
201+
(isQObject && self->_obj && self->_obj->parent())) {
202+
self->passOwnershipToCPP();
203+
}
192204
}
193205
} else {
194206
QString error = QString("No constructors available for ") + self->classInfo()->className();

src/PythonQtInstanceWrapper.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,33 @@ typedef struct PythonQtInstanceWrapperStruct {
7373
_objPointerCopy = object;
7474
}
7575

76+
//! Passes the ownership of the wrapped object to C++
77+
void passOwnershipToCPP() {
78+
// we pass the ownership to C++
79+
_ownedByPythonQt = false;
80+
// handle shell instance
81+
if (_isShellInstance) {
82+
if (!_shellInstanceRefCountsWrapper) {
83+
// ref count the wrapper, so that the Python part of the shell instance can not go
84+
// away until the C++ object gets deleted...
85+
Py_INCREF((PyObject*)this);
86+
_shellInstanceRefCountsWrapper = true;
87+
}
88+
}
89+
}
90+
91+
//! Passes the ownership to Python
92+
void passOwnershipToPython() {
93+
_ownedByPythonQt = true;
94+
// if the shell instance was owned by C++ and the ownership goes to Python,
95+
// we need to remove the extra ref count that kept the Python part alive from the C++ side.
96+
if (_shellInstanceRefCountsWrapper) {
97+
Py_DECREF((PyObject*)this);
98+
_shellInstanceRefCountsWrapper = false;
99+
}
100+
}
101+
102+
76103
//! pointer to the wrapped Qt object or if _wrappedPtr is set, the Qt object that wraps the C++ Ptr
77104
QPointer<QObject> _obj;
78105
//! a copy of the _obj pointer, which is required because the wrapper needs to
@@ -93,6 +120,9 @@ typedef struct PythonQtInstanceWrapperStruct {
93120
//! stores if the object is a shell instance
94121
bool _isShellInstance;
95122

123+
//! stores if the shell instance (C++) owns the wrapper with its ref count
124+
bool _shellInstanceRefCountsWrapper;
125+
96126
} PythonQtInstanceWrapper;
97127

98128
int PythonQtInstanceWrapper_init(PythonQtInstanceWrapper * self, PyObject * args, PyObject * kwds);

src/PythonQtMethodInfo.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,27 @@ void PythonQtMethodInfo::fillParameterInfo(ParameterInfo& type, const QByteArray
120120
type.enumWrapper = NULL;
121121
type.innerNamePointerCount = 0;
122122
type.isQList = false;
123-
123+
type.passOwnershipToCPP = false;
124+
type.passOwnershipToPython = false;
125+
type.newOwnerOfThis = false;
126+
124127
int len = name.length();
125128
if (len>0) {
129+
if (name.startsWith("PythonQtPassOwnershipToCPP<")) {
130+
type.passOwnershipToCPP = true;
131+
name = name.mid(27, len-28);
132+
len -= 28;
133+
}
134+
if (name.startsWith("PythonQtPassOwnershipToPython<")) {
135+
type.passOwnershipToPython = true;
136+
name = name.mid(30, len-31);
137+
len -= 31;
138+
}
139+
if (name.startsWith("PythonQtNewOwnerOfThis<")) {
140+
type.newOwnerOfThis = true;
141+
name = name.mid(23, len-24);
142+
len -= 24;
143+
}
126144
if (strncmp(name.constData(), "const ", 6)==0) {
127145
name = name.mid(6);
128146
len -= 6;

src/PythonQtMethodInfo.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class PYTHONQT_EXPORT PythonQtMethodInfo
7373
bool isConst;
7474
bool isReference;
7575
bool isQList;
76+
bool passOwnershipToCPP;
77+
bool passOwnershipToPython;
78+
bool newOwnerOfThis;
7679
};
7780

7881
PythonQtMethodInfo() {};

0 commit comments

Comments
 (0)