Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
CustomPyTypes.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "CustomPyTypes.h"
4#include "CPPInstance.h"
5#include "Converters.h"
6#include "ProxyWrappers.h"
7#include "PyStrings.h"
8
9// As of Python 3.12, we can't use the PyMethod_GET_FUNCTION and
10// PyMethod_GET_SELF macros anymore, as the contain asserts that check if the
11// Python type is actually PyMethod_Type. If the Python type is
12// CustomInstanceMethod_Type, we need our own macros. Technically they do they
13// same, because the actual C++ type of the PyObject is PyMethodObject anyway.
14#define CustomInstanceMethod_GET_SELF(meth) reinterpret_cast<PyMethodObject *>(meth)->im_self
15#define CustomInstanceMethod_GET_FUNCTION(meth) reinterpret_cast<PyMethodObject *>(meth)->im_func
16#if PY_VERSION_HEX >= 0x03000000
17// TODO: this will break functionality
18#define CustomInstanceMethod_GET_CLASS(meth) Py_None
19#else
20#define CustomInstanceMethod_GET_CLASS(meth) PyMethod_GET_CLASS(meth)
21#endif
22
23
24namespace CPyCppyy {
25
26//= float type allowed for reference passing =================================
27PyTypeObject RefFloat_Type = { // python float is a C/C++ double
28 PyVarObject_HEAD_INIT(&PyType_Type, 0)
29 (char*)"cppyy.Double", // tp_name
30 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
31 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
32 Py_TPFLAGS_BASETYPE, // tp_flags
33 (char*)"CPyCppyy float object for pass by reference", // tp_doc
34 0, 0, 0, 0, 0, 0, 0, 0, 0,
35 &PyFloat_Type, // tp_base
36 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
37#if PY_VERSION_HEX >= 0x02030000
38 , 0 // tp_del
39#endif
40#if PY_VERSION_HEX >= 0x02060000
41 , 0 // tp_version_tag
42#endif
43#if PY_VERSION_HEX >= 0x03040000
44 , 0 // tp_finalize
45#endif
46};
47
48//= long type allowed for reference passing ==================================
49PyTypeObject RefInt_Type = { // python int is a C/C++ long
50 PyVarObject_HEAD_INIT(&PyType_Type, 0)
51 (char*)"cppyy.Long", // tp_name
52 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
53 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
54 Py_TPFLAGS_BASETYPE
55#if PY_VERSION_HEX >= 0x03040000
56 | Py_TPFLAGS_LONG_SUBCLASS
57#endif
58 , // tp_flags
59 (char*)"CPyCppyy long object for pass by reference", // tp_doc
60 0, 0, 0, 0, 0, 0, 0, 0, 0,
61 &PyInt_Type, // tp_base
62 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
63#if PY_VERSION_HEX >= 0x02030000
64 , 0 // tp_del
65#endif
66#if PY_VERSION_HEX >= 0x02060000
67 , 0 // tp_version_tag
68#endif
69#if PY_VERSION_HEX >= 0x03040000
70 , 0 // tp_finalize
71#endif
72};
73
74//- custom type representing typedef to pointer of class ---------------------
76{
77 long long addr = 0;
78 if (!PyArg_ParseTuple(args, const_cast<char*>("|L"), &addr))
79 return nullptr;
80 return BindCppObjectNoCast((Cppyy::TCppObject_t)(intptr_t)addr, self->fType);
81}
82
84 PyVarObject_HEAD_INIT(&PyType_Type, 0)
85 (char*)"cppyy.TypedefPointerToClass",// tp_name
86 sizeof(typedefpointertoclassobject), // tp_basicsize
87 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
88 (ternaryfunc)tpc_call, // tp_call
89 0, 0, 0, 0,
90 Py_TPFLAGS_DEFAULT, // tp_flags
91 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
92#if PY_VERSION_HEX >= 0x02030000
93 , 0 // tp_del
94#endif
95#if PY_VERSION_HEX >= 0x02060000
96 , 0 // tp_version_tag
97#endif
98#if PY_VERSION_HEX >= 0x03040000
99 , 0 // tp_finalize
100#endif
101};
102
103//= instancemethod object with a more efficient call function ================
104static PyMethodObject* free_list;
105static int numfree = 0;
106#ifndef PyMethod_MAXFREELIST
107#define PyMethod_MAXFREELIST 256
108#endif
109
110//-----------------------------------------------------------------------------
112#if PY_VERSION_HEX < 0x03000000
113 pyclass
114#endif
115 )
116{
117// from instancemethod, but with custom type (at issue is that instancemethod is not
118// meant to be derived from)
119 PyMethodObject* im;
120 if (!PyCallable_Check(func)) {
121 PyErr_Format(PyExc_SystemError,
122 "%s:%d: bad argument to internal function", __FILE__, __LINE__);
123 return nullptr;
124 }
125
126 im = free_list;
127 if (im != nullptr) {
128 free_list = (PyMethodObject*)(im->im_self);
129 (void)PyObject_INIT(im, &CustomInstanceMethod_Type);
130 }
131 else {
132 im = PyObject_GC_New(PyMethodObject, &CustomInstanceMethod_Type);
133 if (im == nullptr)
134 return nullptr;
135 }
136
137 im->im_weakreflist = nullptr;
138 Py_INCREF(func);
139 im->im_func = func;
140 Py_XINCREF(self);
141 im->im_self = self;
142#if PY_VERSION_HEX < 0x03000000
143 Py_XINCREF(pyclass);
144 im->im_class = pyclass;
145#endif
146 PyObject_GC_Track(im);
147 return (PyObject*)im;
148}
149
150//-----------------------------------------------------------------------------
151static void im_dealloc(PyMethodObject* im)
152{
153// from instancemethod, but with custom type (at issue is that instancemethod is not
154// meant to be derived from)
155 PyObject_GC_UnTrack(im);
156
157 if (im->im_weakreflist != nullptr)
158 PyObject_ClearWeakRefs((PyObject*)im);
159
160 Py_DECREF(im->im_func);
161 Py_XDECREF(im->im_self);
162#if PY_VERSION_HEX < 0x03000000
163 Py_XDECREF(im->im_class);
164#endif
165
167 im->im_self = (PyObject*)free_list;
168 free_list = im;
169 numfree++;
170 } else {
171 PyObject_GC_Del(im);
172 }
173}
174
175//-----------------------------------------------------------------------------
176static PyObject* im_call(PyObject* meth, PyObject* args, PyObject* kw)
177{
178// The mapping from a method to a function involves reshuffling of self back
179// into the list of arguments. However, the pythonized methods will then have
180// to undo that shuffling, which is inefficient. This method is the same as
181// the one for the instancemethod object, except for the shuffling.
183
184 if (!self) {
185 // unbound methods must be called with an instance of the class (or a
186 // derived class) as first argument
187 Py_ssize_t argc = PyTuple_GET_SIZE(args);
189 if (1 <= argc && PyObject_IsInstance(PyTuple_GET_ITEM(args, 0), pyclass) == 1) {
190 self = PyTuple_GET_ITEM(args, 0);
191
192 PyObject* newArgs = PyTuple_New(argc-1);
193 for (int i = 1; i < argc; ++i) {
194 PyObject* v = PyTuple_GET_ITEM(args, i);
195 Py_INCREF(v);
196 PyTuple_SET_ITEM(newArgs, i-1, v);
197 }
198
199 args = newArgs;
200
201 } else
202 return PyMethod_Type.tp_call(meth, args, kw); // will set proper error msg
203
204 } else
205 Py_INCREF(args);
206
207 PyCFunctionObject* func = (PyCFunctionObject*)CustomInstanceMethod_GET_FUNCTION(meth);
208
209// the function is globally shared, so set and reset its "self" (ok, b/c of GIL)
210 Py_INCREF(self);
211 func->m_self = self;
213 func->m_self = nullptr;
214 Py_DECREF(self);
215 Py_DECREF(args);
216 return result;
217}
218
219//-----------------------------------------------------------------------------
220static PyObject* im_descr_get(PyObject* meth, PyObject* obj, PyObject* pyclass)
221{
222
223// from instancemethod: don't rebind an already bound method, or an unbound method
224// of a class that's not a base class of pyclass
226#if PY_VERSION_HEX < 0x03000000
228 !PyObject_IsSubclass(pyclass, CustomInstanceMethod_GET_CLASS(meth)))
229#endif
230 ) {
231 Py_INCREF(meth);
232 return meth;
233 }
234
235 if (obj == Py_None)
236 obj = nullptr;
237
239}
240
241//= CPyCppyy custom instance method type =====================================
243 PyVarObject_HEAD_INIT(&PyType_Type, 0)
244 (char*)"cppyy.InstanceMethod", // tp_name
245 0, 0,
246 (destructor)im_dealloc, // tp_dealloc
247 0, 0, 0, 0, 0, 0, 0, 0, 0,
248 im_call, // tp_call
249 0, 0, 0, 0,
250 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES |
251 Py_TPFLAGS_BASETYPE, // tp_flags
252 (char*)"CPyCppyy custom instance method (internal)", // tp_doc
253 0, 0, 0, 0, 0, 0, 0, 0, 0,
254 &PyMethod_Type, // tp_base
255 0,
256 im_descr_get, // tp_descr_get
257 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
258#if PY_VERSION_HEX >= 0x02030000
259 , 0 // tp_del
260#endif
261#if PY_VERSION_HEX >= 0x02060000
262 , 0 // tp_version_tag
263#endif
264#if PY_VERSION_HEX >= 0x03040000
265 , 0 // tp_finalize
266#endif
267};
268
269
270//= CPyCppyy custom iterator for performance =================================
272 Py_XDECREF(ii->ii_container);
273 PyObject_GC_Del(ii);
274}
275
276static int indexiter_traverse(indexiterobject* ii, visitproc visit, void* arg) {
277 Py_VISIT(ii->ii_container);
278 return 0;
279}
280
282 if (ii->ii_pos >= ii->ii_len)
283 return nullptr;
284
285 PyObject* pyindex = PyLong_FromSsize_t(ii->ii_pos);
286 PyObject* result = PyObject_CallMethodObjArgs((PyObject*)ii->ii_container, PyStrings::gGetItem, pyindex, nullptr);
287 Py_DECREF(pyindex);
288
289 ii->ii_pos += 1;
290 return result;
291}
292
293PyTypeObject IndexIter_Type = {
294 PyVarObject_HEAD_INIT(&PyType_Type, 0)
295 (char*)"cppyy.indexiter", // tp_name
296 sizeof(indexiterobject), // tp_basicsize
297 0,
298 (destructor)indexiter_dealloc, // tp_dealloc
299 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
300 Py_TPFLAGS_DEFAULT |
301 Py_TPFLAGS_HAVE_GC, // tp_flags
302 0,
303 (traverseproc)indexiter_traverse, // tp_traverse
304 0, 0, 0,
305 PyObject_SelfIter, // tp_iter
306 (iternextfunc)indexiter_iternext, // tp_iternext
307 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
308#if PY_VERSION_HEX >= 0x02030000
309 , 0 // tp_del
310#endif
311#if PY_VERSION_HEX >= 0x02060000
312 , 0 // tp_version_tag
313#endif
314#if PY_VERSION_HEX >= 0x03040000
315 , 0 // tp_finalize
316#endif
317};
318
319
321 if (vi->vi_converter && vi->vi_converter->HasState()) delete vi->vi_converter;
323}
324
326 if (vi->ii_pos >= vi->ii_len)
327 return nullptr;
328
329 PyObject* result = nullptr;
330
331 if (vi->vi_data && vi->vi_converter) {
332 void* location = (void*)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos);
333 result = vi->vi_converter->FromMemory(location);
334 } else if (vi->vi_data && vi->vi_klass) {
335 // The CPPInstance::kNoMemReg by-passes the memory regulator; the assumption here is
336 // that objects in vectors are simple and thus do not need to maintain object identity
337 // (or at least not during the loop anyway). This gains 2x in performance.
338 Cppyy::TCppObject_t cppobj = (Cppyy::TCppObject_t)((ptrdiff_t)vi->vi_data + vi->vi_stride * vi->ii_pos);
341 PyObject_SetAttr(result, PyStrings::gLifeLine, vi->ii_container);
342 } else {
343 PyObject* pyindex = PyLong_FromSsize_t(vi->ii_pos);
344 result = PyObject_CallMethodObjArgs((PyObject*)vi->ii_container, PyStrings::gGetNoCheck, pyindex, nullptr);
345 Py_DECREF(pyindex);
346 }
347
348 vi->ii_pos += 1;
349 return result;
350}
351
352PyTypeObject VectorIter_Type = {
353 PyVarObject_HEAD_INIT(&PyType_Type, 0)
354 (char*)"cppyy.vectoriter", // tp_name
355 sizeof(vectoriterobject), // tp_basicsize
356 0,
357 (destructor)vectoriter_dealloc, // tp_dealloc
358 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
359 Py_TPFLAGS_DEFAULT |
360 Py_TPFLAGS_HAVE_GC, // tp_flags
361 0,
362 (traverseproc)indexiter_traverse, // tp_traverse
363 0, 0, 0,
364 PyObject_SelfIter, // tp_iter
365 (iternextfunc)vectoriter_iternext, // tp_iternext
366 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
367#if PY_VERSION_HEX >= 0x02030000
368 , 0 // tp_del
369#endif
370#if PY_VERSION_HEX >= 0x02060000
371 , 0 // tp_version_tag
372#endif
373#if PY_VERSION_HEX >= 0x03040000
374 , 0 // tp_finalize
375#endif
376};
377
378} // namespace CPyCppyy
int Py_ssize_t
Definition CPyCppyy.h:236
#define CPyCppyy_PyCFunction_Call
Definition CPyCppyy.h:304
#define PyVarObject_HEAD_INIT(type, size)
Definition CPyCppyy.h:215
#define PyMethod_MAXFREELIST
#define CustomInstanceMethod_GET_SELF(meth)
#define CustomInstanceMethod_GET_FUNCTION(meth)
#define CustomInstanceMethod_GET_CLASS(meth)
_object PyObject
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
virtual bool HasState()
Definition API.h:105
virtual PyObject * FromMemory(void *address)
PyObject * gLifeLine
Definition PyStrings.cxx:25
PyObject * gGetItem
Definition PyStrings.cxx:19
PyObject * gGetNoCheck
Definition PyStrings.cxx:20
Set of helper functions that are invoked from the pythonizors, on the Python side.
static PyObject * indexiter_iternext(indexiterobject *ii)
PyObject * CustomInstanceMethod_New(PyObject *func, PyObject *self, PyObject *pyclass)
PyTypeObject VectorIter_Type
PyTypeObject CustomInstanceMethod_Type
static PyMethodObject * free_list
PyTypeObject RefFloat_Type
Custom "builtins," detectable by type, for pass by ref and improved performance.
static int numfree
PyTypeObject TypedefPointerToClass_Type
PyObject * BindCppObjectNoCast(Cppyy::TCppObject_t object, Cppyy::TCppType_t klass, const unsigned flags=0)
static void vectoriter_dealloc(vectoriterobject *vi)
static int indexiter_traverse(indexiterobject *ii, visitproc visit, void *arg)
static PyObject * im_descr_get(PyObject *meth, PyObject *obj, PyObject *pyclass)
static PyObject * vectoriter_iternext(vectoriterobject *vi)
bool CPPInstance_Check(T *object)
PyTypeObject IndexIter_Type
static void indexiter_dealloc(indexiterobject *ii)
static PyObject * tpc_call(typedefpointertoclassobject *self, PyObject *args, PyObject *)
static void im_dealloc(PyMethodObject *im)
static PyObject * im_call(PyObject *meth, PyObject *args, PyObject *kw)
PyTypeObject RefInt_Type
void * TCppObject_t
Definition cpp_cppyy.h:21
PyObject_HEAD PyObject * ii_container
PyObject_HEAD Cppyy::TCppType_t fType
Cppyy::TCppType_t vi_klass
CPyCppyy::Converter * vi_converter