Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
CPPDataMember.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "CPyCppyy/Reflex.h"
4#include "PyStrings.h"
5#include "CPPDataMember.h"
6#include "CPPInstance.h"
7#include "Dimensions.h"
8#include "LowLevelViews.h"
9#include "ProxyWrappers.h"
10#include "PyStrings.h"
11#include "TypeManip.h"
12#include "Utility.h"
13
14// Standard
15#include <algorithm>
16#include <vector>
17#include <limits.h>
18#include <structmember.h>
19
20namespace CPyCppyy {
21
23 kNone = 0x0000,
24 kIsStaticData = 0x0001,
25 kIsConstData = 0x0002,
26 kIsArrayType = 0x0004,
27 kIsEnumPrep = 0x0008,
28 kIsEnumType = 0x0010,
29 kIsCachable = 0x0020
30};
31
32//= CPyCppyy data member as Python property behavior =========================
33static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, PyObject* /* kls */)
34{
35// cache lookup for low level views
36 if (pyobj && dm->fFlags & kIsCachable) {
38 for (auto it = cache.begin(); it != cache.end(); ++it) {
39 if (it->first == dm->fOffset) {
40 if (it->second) {
41 Py_INCREF(it->second);
42 return it->second;
43 } else
44 cache.erase(it);
45 break;
46 }
47 }
48 }
49
50// non-initialized or public data accesses through class (e.g. by help())
51 void* address = dm->GetAddress(pyobj);
52 if (!address || (intptr_t)address == -1 /* Cling error */)
53 return nullptr;
54
55 if (dm->fFlags & (kIsEnumPrep | kIsEnumType)) {
56 if (dm->fFlags & kIsEnumPrep) {
57 // still need to do lookup; only ever try this once, then fallback on converter
58 dm->fFlags &= ~kIsEnumPrep;
59
60 // fDescription contains the full name of the actual enum value object
61 const std::string& lookup = CPyCppyy_PyText_AsString(dm->fDescription);
62 const std::string& enum_type = TypeManip::extract_namespace(lookup);
63 const std::string& enum_scope = TypeManip::extract_namespace(enum_type);
64
65 PyObject* pyscope = nullptr;
66 if (enum_scope.empty()) pyscope = GetScopeProxy(Cppyy::gGlobalScope);
67 else pyscope = CreateScopeProxy(enum_scope);
68 if (pyscope) {
69 PyObject* pyEnumType = PyObject_GetAttrString(pyscope,
70 enum_type.substr(enum_scope.size() ? enum_scope.size()+2 : 0, std::string::npos).c_str());
71 if (pyEnumType) {
72 PyObject* pyval = PyObject_GetAttrString(pyEnumType,
73 lookup.substr(enum_type.size()+2, std::string::npos).c_str());
74 Py_DECREF(pyEnumType);
75 if (pyval) {
76 Py_DECREF(dm->fDescription);
77 dm->fDescription = pyval;
78 dm->fFlags |= kIsEnumType;
79 }
80 }
81 Py_DECREF(pyscope);
82 }
83 if (!(dm->fFlags & kIsEnumType))
84 PyErr_Clear();
85 }
86
87 if (dm->fFlags & kIsEnumType) {
88 Py_INCREF(dm->fDescription);
89 return dm->fDescription;
90 }
91 }
92
93 if (dm->fConverter != 0) {
94 PyObject* result = dm->fConverter->FromMemory((dm->fFlags & kIsArrayType) ? &address : address);
95 if (!result)
96 return result;
97
98 // low level views are expensive to create, so cache them on the object instead
99 bool isLLView = LowLevelView_CheckExact(result);
100 if (isLLView && CPPInstance_Check(pyobj)) {
101 Py_INCREF(result);
102 pyobj->GetDatamemberCache().push_back(std::make_pair(dm->fOffset, result));
103 dm->fFlags |= kIsCachable;
104 }
105
106 // ensure that the encapsulating class does not go away for the duration
107 // of the data member's lifetime, if it is a bound type (it doesn't matter
108 // for builtin types, b/c those are copied over into python types and thus
109 // end up being "stand-alone")
110 // TODO: should be done for LLViews as well
111 else if (pyobj && !(dm->fFlags & kIsStaticData) && CPPInstance_Check(result)) {
112 if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pyobj) == -1)
113 PyErr_Clear(); // ignored
114 }
115
116 return result;
117 }
118
119 PyErr_Format(PyExc_NotImplementedError,
120 "no converter available for \"%s\"", dm->GetName().c_str());
121 return nullptr;
122}
123
124//-----------------------------------------------------------------------------
126{
127// Set the value of the C++ datum held.
128 const int errret = -1;
129
130 if (!value) {
131 // we're being deleted; fine for namespaces (redo lookup next time), but makes
132 // no sense for classes/structs
134 PyErr_SetString(PyExc_TypeError, "data member deletion is not supported");
135 return errret;
136 }
137
138 // deletion removes the proxy, with the idea that a fresh lookup can be made,
139 // to support Cling's shadowing of declarations (TODO: the use case here is
140 // redeclared variables, for which fDescription is indeed th ename; it might
141 // fail for enums).
142 return PyObject_DelAttr((PyObject*)Py_TYPE(pyobj), dm->fDescription);
143 }
144
145// filter const objects to prevent changing their values
146 if (dm->fFlags & kIsConstData) {
147 PyErr_SetString(PyExc_TypeError, "assignment to const data not allowed");
148 return errret;
149 }
150
151// remove cached low level view, if any (will be restored upon reaeding)
152 if (dm->fFlags & kIsCachable) {
154 for (auto it = cache.begin(); it != cache.end(); ++it) {
155 if (it->first == dm->fOffset) {
156 Py_XDECREF(it->second);
157 cache.erase(it);
158 break;
159 }
160 }
161 }
162
163 intptr_t address = (intptr_t)dm->GetAddress(pyobj);
164 if (!address || address == -1 /* Cling error */)
165 return errret;
166
167// for fixed size arrays
168 void* ptr = (void*)address;
169 if (dm->fFlags & kIsArrayType)
170 ptr = &address;
171
172// actual conversion; return on success
173 if (dm->fConverter && dm->fConverter->ToMemory(value, ptr, (PyObject*)pyobj))
174 return 0;
175
176// set a python error, if not already done
177 if (!PyErr_Occurred())
178 PyErr_SetString(PyExc_RuntimeError, "property type mismatch or assignment not allowed");
179
180// failure ...
181 return errret;
182}
183
184//= CPyCppyy data member construction/destruction ===========================
185static CPPDataMember* dm_new(PyTypeObject* pytype, PyObject*, PyObject*)
186{
187// Create and initialize a new property descriptor.
188 CPPDataMember* dm = (CPPDataMember*)pytype->tp_alloc(pytype, 0);
189
190 dm->fOffset = 0;
191 dm->fFlags = 0;
192 dm->fConverter = nullptr;
193 dm->fEnclosingScope = 0;
194 dm->fDescription = nullptr;
195 dm->fDoc = nullptr;
196
197 new (&dm->fFullType) std::string{};
198
199 return dm;
200}
201
202//----------------------------------------------------------------------------
203static void dm_dealloc(CPPDataMember* dm)
204{
205// Deallocate memory held by this descriptor.
206 using namespace std;
207 if (dm->fConverter && dm->fConverter->HasState()) delete dm->fConverter;
208 Py_XDECREF(dm->fDescription); // never exposed so no GC necessary
209 Py_XDECREF(dm->fDoc);
210
211 dm->fFullType.~string();
212
213 Py_TYPE(dm)->tp_free((PyObject*)dm);
214}
215
216static PyMemberDef dm_members[] = {
217 {(char*)"__doc__", T_OBJECT, offsetof(CPPDataMember, fDoc), 0,
218 (char*)"writable documentation"},
219 {NULL, 0, 0, 0, nullptr} /* Sentinel */
220};
221
222//= CPyCppyy datamember proxy access to internals ============================
224{
225// Provide the requested reflection information.
226 Cppyy::Reflex::RequestId_t request = -1;
228 if (!PyArg_ParseTuple(args, const_cast<char*>("i|i:__cpp_reflex__"), &request, &format))
229 return nullptr;
230
231 if (request == Cppyy::Reflex::TYPE) {
233 return CPyCppyy_PyText_FromString(dm->fFullType.c_str());
234 } else if (request == Cppyy::Reflex::OFFSET) {
236 return PyLong_FromLong(dm->fOffset);
237 }
238
239 PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d", request, format);
240 return nullptr;
241}
242
243//----------------------------------------------------------------------------
244static PyMethodDef dm_methods[] = {
245 {(char*)"__cpp_reflex__", (PyCFunction)dm_reflex, METH_VARARGS,
246 (char*)"C++ datamember reflection information" },
247 {(char*)nullptr, nullptr, 0, nullptr }
248};
249
250
251//= CPyCppyy data member type ================================================
252PyTypeObject CPPDataMember_Type = {
253 PyVarObject_HEAD_INIT(&PyType_Type, 0)
254 (char*)"cppyy.CPPDataMember", // tp_name
255 sizeof(CPPDataMember), // tp_basicsize
256 0, // tp_itemsize
257 (destructor)dm_dealloc, // tp_dealloc
258 0, // tp_vectorcall_offset / tp_print
259 0, // tp_getattr
260 0, // tp_setattr
261 0, // tp_as_async / tp_compare
262 0, // tp_repr
263 0, // tp_as_number
264 0, // tp_as_sequence
265 0, // tp_as_mapping
266 0, // tp_hash
267 0, // tp_call
268 0, // tp_str
269 0, // tp_getattro
270 0, // tp_setattro
271 0, // tp_as_buffer
272 Py_TPFLAGS_DEFAULT, // tp_flags
273 (char*)"cppyy data member (internal)", // tp_doc
274 0, // tp_traverse
275 0, // tp_clear
276 0, // tp_richcompare
277 0, // tp_weaklistoffset
278 0, // tp_iter
279 0, // tp_iternext
280 dm_methods, // tp_methods
281 dm_members, // tp_members
282 0, // tp_getset
283 0, // tp_base
284 0, // tp_dict
285 (descrgetfunc)dm_get, // tp_descr_get
286 (descrsetfunc)dm_set, // tp_descr_set
287 0, // tp_dictoffset
288 0, // tp_init
289 0, // tp_alloc
290 (newfunc)dm_new, // tp_new
291 0, // tp_free
292 0, // tp_is_gc
293 0, // tp_bases
294 0, // tp_mro
295 0, // tp_cache
296 0, // tp_subclasses
297 0 // tp_weaklist
298#if PY_VERSION_HEX >= 0x02030000
299 , 0 // tp_del
300#endif
301#if PY_VERSION_HEX >= 0x02060000
302 , 0 // tp_version_tag
303#endif
304#if PY_VERSION_HEX >= 0x03040000
305 , 0 // tp_finalize
306#endif
307#if PY_VERSION_HEX >= 0x03080000
308 , 0 // tp_vectorcall
309#endif
310#if PY_VERSION_HEX >= 0x030c0000
311 , 0 // tp_watched
312#endif
313};
314
315} // namespace CPyCppyy
316
317
318//- public members -----------------------------------------------------------
320{
321 fEnclosingScope = scope;
322 fOffset = Cppyy::GetDatamemberOffset(scope, idata); // TODO: make lazy
323 fFlags = Cppyy::IsStaticData(scope, idata) ? kIsStaticData : 0;
324
325 std::vector<dim_t> dims;
326 int ndim = 0; Py_ssize_t size = 0;
327 while (0 < (size = Cppyy::GetDimensionSize(scope, idata, ndim))) {
328 ndim += 1;
329 if (size == INT_MAX) // meaning: incomplete array type
331 if (ndim == 1) dims.reserve(4);
332 dims.push_back((dim_t)size);
333 }
334 if (!dims.empty())
336
337 const std::string name = Cppyy::GetDatamemberName(scope, idata);
338 fFullType = Cppyy::GetDatamemberType(scope, idata);
339 if (Cppyy::IsEnumData(scope, idata)) {
340 if (fFullType.find("(anonymous)") == std::string::npos &&
341 fFullType.find("(unnamed)") == std::string::npos) {
342 // repurpose fDescription for lazy lookup of the enum later
345 }
348 } else if (Cppyy::IsConstData(scope, idata)) {
350 }
351
352// if this data member is an array, the conversion needs to be pointer to object for instances,
353// to prevent the need for copying in the conversion; furthermore, fixed arrays' full type for
354// builtins are not declared as such if more than 1-dim (TODO: fix in clingwrapper)
355 if (!dims.empty() && fFullType.back() != '*') {
357 else if (fFullType.back() != ']') {
358 for (auto d: dims) fFullType += d == UNKNOWN_SIZE ? "*" : "[]";
359 }
360 }
361
362 if (dims.empty())
364 else
365 fConverter = CreateConverter(fFullType, {(dim_t)dims.size(), dims.data()});
366
367 if (!(fFlags & kIsEnumPrep))
369}
370
371//-----------------------------------------------------------------------------
372void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, const std::string& name, void* address)
373{
374 fEnclosingScope = scope;
375 fDescription = CPyCppyy_PyText_FromString(name.c_str());
376 fOffset = (intptr_t)address;
378 fConverter = CreateConverter("internal_enum_type_t");
379 fFullType = "unsigned int";
380}
381
382
383//-----------------------------------------------------------------------------
385{
386// class attributes, global properties
387 if (fFlags & kIsStaticData)
388 return (void*)fOffset;
389
390// special case: non-static lookup through class
391 if (!pyobj) {
392 PyErr_SetString(PyExc_AttributeError, "attribute access requires an instance");
393 return nullptr;
394 }
395
396// instance attributes; requires valid object for full address
397 if (!CPPInstance_Check(pyobj)) {
398 PyErr_Format(PyExc_TypeError,
399 "object instance required for access to property \"%s\"", GetName().c_str());
400 return nullptr;
401 }
402
403 void* obj = pyobj->GetObject();
404 if (!obj) {
405 PyErr_SetString(PyExc_ReferenceError, "attempt to access a null-pointer");
406 return nullptr;
407 }
408
409// the proxy's internal offset is calculated from the enclosing class
410 ptrdiff_t offset = 0;
411 Cppyy::TCppType_t oisa = pyobj->ObjectIsA();
412 if (oisa != fEnclosingScope)
413 offset = Cppyy::GetBaseOffset(oisa, fEnclosingScope, obj, 1 /* up-cast */);
414
415 return (void*)((intptr_t)obj + offset + fOffset);
416}
417
418
419//-----------------------------------------------------------------------------
421{
422 if (fFlags & kIsEnumType) {
423 PyObject* repr = PyObject_Repr(fDescription);
424 if (repr) {
425 std::string res = CPyCppyy_PyText_AsString(repr);
426 Py_DECREF(repr);
427 return res;
428 }
429 PyErr_Clear();
430 return "<unknown>";
431 } else if (fFlags & kIsEnumPrep) {
432 std::string fullName = CPyCppyy_PyText_AsString(fDescription);
433 return fullName.substr(fullName.rfind("::")+2, std::string::npos);
434 }
435
436 return CPyCppyy_PyText_AsString(fDescription);
437}
#define Py_TYPE(ob)
Definition CPyCppyy.h:196
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:76
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:81
#define PyVarObject_HEAD_INIT(type, size)
Definition CPyCppyy.h:194
Converter * fConverter
_object PyObject
std::ios_base::fmtflags fFlags
#define d(i)
Definition RSha256.hxx:102
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
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
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
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 Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t format
char name[80]
Definition TGX11.cxx:110
PyObject_HEAD intptr_t fOffset
Cppyy::TCppScope_t fEnclosingScope
void * GetAddress(CPPInstance *pyobj)
void Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata)
CI_DatamemberCache_t & GetDatamemberCache()
Cppyy::TCppType_t ObjectIsA(bool check_smart=true) const
virtual bool HasState()
Definition API.h:122
virtual bool ToMemory(PyObject *value, void *address, PyObject *ctxt=nullptr)
virtual PyObject * FromMemory(void *address)
PyObject * gLifeLine
Definition PyStrings.cxx:28
std::string extract_namespace(const std::string &name)
PyObject * GetScopeProxy(Cppyy::TCppScope_t)
Py_ssize_t dim_t
Definition API.h:90
PyObject * CreateScopeProxy(Cppyy::TCppScope_t, const unsigned flags=0)
static PyObject * dm_get(CPPDataMember *dm, CPPInstance *pyobj, PyObject *)
std::vector< std::pair< ptrdiff_t, PyObject * > > CI_DatamemberCache_t
Definition CPPInstance.h:24
static const dim_t UNKNOWN_SIZE
Definition Dimensions.h:11
bool LowLevelView_CheckExact(T *object)
bool CPPInstance_Check(T *object)
static void dm_dealloc(CPPDataMember *dm)
static PyMemberDef dm_members[]
static PyMethodDef dm_methods[]
static CPPDataMember * dm_new(PyTypeObject *pytype, PyObject *, PyObject *)
static int dm_set(CPPDataMember *dm, CPPInstance *pyobj, PyObject *value)
CPYCPPYY_EXTERN Converter * CreateConverter(const std::string &name, cdims_t=0)
static PyObject * dm_reflex(CPPDataMember *dm, PyObject *args)
PyTypeObject CPPDataMember_Type
const RequestId_t TYPE
Definition Reflex.h:19
const FormatId_t AS_STRING
Definition Reflex.h:24
const FormatId_t OPTIMAL
Definition Reflex.h:22
const RequestId_t OFFSET
Definition Reflex.h:17
size_t TCppIndex_t
Definition cpp_cppyy.h:24
RPY_EXPORTED ptrdiff_t GetBaseOffset(TCppType_t derived, TCppType_t base, TCppObject_t address, int direction, bool rerror=false)
RPY_EXPORTED bool IsEnumData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsConstData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED TCppScope_t gGlobalScope
Definition cpp_cppyy.h:53
RPY_EXPORTED int GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:19
RPY_EXPORTED std::string ResolveEnum(const std::string &enum_type)
RPY_EXPORTED bool IsNamespace(TCppScope_t scope)
RPY_EXPORTED bool IsStaticData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
size_t TCppScope_t
Definition cpp_cppyy.h:18
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata)