Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
Dispatcher.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "Dispatcher.h"
4#include "CPPScope.h"
5#include "PyStrings.h"
6#include "ProxyWrappers.h"
7#include "TypeManip.h"
8#include "Utility.h"
9
10// Standard
11#include <set>
12#include <sstream>
13
14
15//----------------------------------------------------------------------------
16static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& mtCppName, std::ostringstream& code)
17{
18// inject implementation for an overridden method
19 using namespace CPyCppyy;
20
21// method declaration
23 code << " " << retType << " " << mtCppName << "(";
24
25// build out the signature with predictable formal names
27 std::vector<std::string> argtypes; argtypes.reserve(nArgs);
28 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
30 if (i != 0) code << ", ";
31 code << argtypes.back() << " arg" << i;
32 }
33 code << ") ";
34 if (Cppyy::IsConstMethod(method)) code << "const ";
35 code << "{\n";
36
37// on destruction, the Python object may go first, in which case provide a diagnostic
38// warning (raising a PyException may not be possible as this could happen during
39// program shutdown); note that this means that the actual result will be the default
40// and the caller may need to act on that, but that's still an improvement over a
41// possible crash
42 code << " PyObject* iself = (PyObject*)_internal_self;\n"
43 " if (!iself || iself == Py_None) {\n"
44 " PyGILState_STATE state = PyGILState_Ensure();\n"
45 " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n"
46 " PyGILState_Release(state);\n"
47 " return";
48 if (retType != "void") {
49 if (retType.back() != '*')
50 code << " (" << CPyCppyy::TypeManip::remove_const(retType) << "){}";
51 else
52 code << " nullptr";
53 }
54 code << ";\n"
55 " }\n";
56
57// start actual function body
58 Utility::ConstructCallbackPreamble(retType, argtypes, code);
59
60 code << " Py_INCREF(iself);\n";
61
62// perform actual method call
63#if PY_VERSION_HEX < 0x03000000
64 code << " PyObject* mtPyName = PyString_FromString(\"" << mtCppName << "\");\n" // TODO: intern?
65#else
66 code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName << "\");\n"
67#endif
68 " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName";
69 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i)
70 code << ", pyargs[" << i << "]";
71 code << ", NULL);\n Py_DECREF(mtPyName);\n Py_DECREF(iself);\n";
72
73// close
74 Utility::ConstructCallbackReturn(retType, (int)nArgs, code);
75}
76
77//----------------------------------------------------------------------------
78namespace {
79 struct BaseInfo {
80 BaseInfo(Cppyy::TCppType_t t, std::string&& bn, std::string&& bns) :
81 btype(t), bname(bn), bname_scoped(bns) {}
83 std::string bname;
84 std::string bname_scoped;
85 };
86
87 typedef std::vector<BaseInfo> BaseInfos_t;
88 typedef std::vector<Cppyy::TCppMethod_t> Ctors_t;
89 typedef std::vector<Ctors_t> AllCtors_t;
90 typedef std::vector<std::pair<Cppyy::TCppMethod_t, size_t>> CtorInfos_t;
91} // unnamed namespace
92
94 const std::string& derivedName, const BaseInfos_t& base_infos, const AllCtors_t& ctors,
95 std::ostringstream& code, const CtorInfos_t& methods = CtorInfos_t{}, size_t idx = 0)
96{
97 if (idx < ctors.size()) {
98 for (const auto& method : ctors[idx]) {
99 size_t argsmin = (size_t)Cppyy::GetMethodReqArgs(method);
100 size_t argsmax = (size_t)Cppyy::GetMethodNumArgs(method);
101 for (size_t i = argsmin; i <= argsmax; ++i) {
103 methods1.emplace_back(method, i);
105 }
106 }
107 } else {
108 // this is as deep as we go; start writing
109 code << " " << derivedName << "(";
110
111 // declare arguments
112 std::vector<size_t> arg_tots; arg_tots.reserve(methods.size());
113 for (Ctors_t::size_type i = 0; i < methods.size(); ++i) {
114 const auto& cinfo = methods[i];
115 if (i != 0 && (arg_tots.back() || 1 < arg_tots.size())) code << ", ";
116 size_t nArgs = cinfo.second;
117 arg_tots.push_back(i == 0 ? nArgs : nArgs+arg_tots.back());
118
119 if (i != 0) code << "__cppyy_internal::Sep*";
120 size_t offset = (i != 0 ? arg_tots[i-1] : 0);
121 for (size_t j = 0; j < nArgs; ++j) {
122 if (i != 0 || j != 0) code << ", ";
123 code << Cppyy::GetMethodArgType(cinfo.first, j) << " a" << (j+offset);
124 }
125 }
126 code << ") : ";
127
128 // pass arguments to base constructors
129 for (BaseInfos_t::size_type i = 0; i < base_infos.size(); ++i) {
130 if (i != 0) code << ", ";
131 code << base_infos[i].bname << "(";
132 size_t first = (i != 0 ? arg_tots[i-1] : 0);
133 for (size_t j = first; j < arg_tots[i]; ++j) {
134 if (j != first) code << ", ";
136 Cppyy::GetMethodArgType(methods[i].first, j-first)) == "&&";
137 if (isRValue) code << "std::move(";
138 code << "a" << j;
139 if (isRValue) code << ")";
140 }
141 code << ")";
142 }
143 code << " {}\n";
144 }
145}
146
147namespace {
148
149using namespace Cppyy;
150
151static inline
152std::vector<TCppIndex_t> FindBaseMethod(TCppScope_t tbase, const std::string mtCppName)
153{
154// Recursively walk the inheritance tree to find the overloads of the named method
155 std::vector<TCppIndex_t> result;
157 if (result.empty()) {
158 for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) {
161 if (!result.empty())
162 break;
163 }
164 }
165 return result;
166}
167
168} // unnamed namespace
169
171{
172// Scan all methods in dct and where it overloads base methods in klass, create
173// dispatchers on the C++ side. Then interject the dispatcher class.
174
176 err << "internal error: expected tuple of bases and proper dictionary";
177 return false;
178 }
179
180 if (!Utility::IncludePython()) {
181 err << "failed to include Python.h";
182 return false;
183 }
184
185// collect all bases, error checking the hierarchy along the way
188 for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) {
190 continue;
191
193
194 if (!basetype) {
195 err << "base class is incomplete";
196 break;
197 }
198
200 err << Cppyy::GetScopedFinalName(basetype) << " is a namespace";
201 break;
202 }
203
205 const std::string& bname = Cppyy::GetScopedFinalName(basetype);
206 PyErr_Warn(PyExc_RuntimeWarning, (char*)("class \""+bname+"\" has no virtual destructor").c_str());
207 }
208
209 base_infos.emplace_back(
211 }
212
213// TODO: check deep hierarchy for multiple inheritance
214 bool isDeepHierarchy = klass->fCppType && base_infos.front().btype != klass->fCppType;
215
216// once classes can be extended, should consider re-use; for now, since derived
217// python classes can differ in what they override, simply use different shims
218 static int counter = 0;
219 std::ostringstream osname;
220 osname << "Dispatcher" << ++counter;
221 const std::string& derivedName = osname.str();
222
223// generate proxy class with the relevant method dispatchers
224 std::ostringstream code;
225
226// start class declaration
227 code << "namespace __cppyy_internal {\n"
228 << "class " << derivedName << " : ";
229 for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) {
230 if (ibase != 0) code << ", ";
231 code << "public ::" << base_infos[ibase].bname_scoped;
232 }
233 code << " {\n";
234 if (!isDeepHierarchy)
235 code << "protected:\n CPyCppyy::DispatchPtr _internal_self;\n";
236 code << "public:\n";
237
238// add a virtual destructor for good measure, which is allowed to be "overridden" by
239// the conventional __destruct__ method (note that __del__ is always called, too, if
240// provided, but only when the Python object goes away; furthermore, if the Python
241// object goes before the C++ one, only __del__ is called)
242 if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) {
243 code << " virtual ~" << derivedName << "() {\n"
244 "PyGILState_STATE state = PyGILState_Ensure();\n"
245 " PyObject* iself = (PyObject*)_internal_self;\n"
246 " if (!iself || iself == Py_None)\n"
247 " return;\n" // safe, as destructor always returns void
248 " Py_INCREF(iself);\n"
249 " PyObject* mtPyName = PyUnicode_FromString(\"__destruct__\");\n"
250 " PyObject* pyresult = PyObject_CallMethodObjArgs(iself, mtPyName, NULL);\n"
251 " Py_DECREF(mtPyName);\n Py_DECREF(iself);\n";
252
253 // this being a destructor, print on exception rather than propagate using the
254 // magic C++ exception ...
255 code << " if (!pyresult) PyErr_Print();\n"
256 " else { Py_DECREF(pyresult); }\n"
257 " PyGILState_Release(state);\n"
258 " }\n";
259 } else
260 code << " virtual ~" << derivedName << "() {}\n";
261
262// methods: first collect all callables, then get overrides from base classes, for
263// those that are still missing, search the hierarchy
266 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) {
270 }
272 if (PyDict_DelItem(clbs, PyStrings::gInit) != 0)
273 PyErr_Clear();
274
275// protected methods and data need their access changed in the C++ trampoline and then
276// exposed on the Python side; so, collect their names as we go along
277 std::set<std::string> protected_names;
278
279// simple case: methods from current class (collect constructors along the way)
280 int has_default = 0, has_cctor = 0, has_ctors = 0, has_tmpl_ctors = 0;
282 for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) {
283 const auto& binfo = base_infos[ibase];
284
286 bool cctor_found = false, default_found = false, any_ctor_found = false;
289
291 any_ctor_found = true;
294 if (nreq == 0) default_found = true;
295 else if (!cctor_found && nreq == 1) {
296 const std::string& argtype = Cppyy::GetMethodArgType(method, 0);
297 if (TypeManip::compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == binfo.bname_scoped)
298 cctor_found = true;
299 }
300 ctors[ibase].push_back(method);
301 }
302 continue;
303 }
304
307 int contains = PyDict_Contains(dct, key);
308 if (contains == -1) PyErr_Clear();
309 if (contains != 1) {
310 Py_DECREF(key);
311
312 // if the method is protected, we expose it through re-declaration and forwarding (using
313 // does not work here b/c there may be private overloads)
316
317 code << " " << Cppyy::GetMethodResultType(method) << " " << mtCppName << "(";
319 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
320 if (i != 0) code << ", ";
321 code << Cppyy::GetMethodArgType(method, i) << " arg" << i;
322 }
323 code << ") ";
324 if (Cppyy::IsConstMethod(method)) code << "const ";
325 code << "{\n return " << binfo.bname << "::" << mtCppName << "(";
326 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
327 if (i != 0) code << ", ";
328 code << "arg" << i;
329 }
330 code << ");\n }\n";
331 }
332
333 continue;
334 }
335
337
338 if (PyDict_DelItem(clbs, key) != 0)
339 PyErr_Clear(); // happens for overloads
340 Py_DECREF(key);
341 }
342
343 // support for templated ctors in single inheritance (TODO: also multi possible?)
344 if (base_infos.size() == 1) {
348 any_ctor_found = true;
349 has_tmpl_ctors += 1;
350 break; // one suffices to map as argument packs are used
351 }
352 }
353 }
354
355 // count the cctors and default ctors to determine whether each base has one
359 }
360
361// try to locate left-overs in base classes
362 for (const auto& binfo : base_infos) {
363 if (PyDict_Size(clbs)) {
364 size_t nbases = Cppyy::GetNumBases(binfo.btype);
365 for (size_t ibase = 0; ibase < nbases; ++ibase) {
368
369 PyObject* keys = PyDict_Keys(clbs);
370 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) {
371 // TODO: should probably invert this looping; but that makes handling overloads clunky
372 PyObject* key = PyList_GET_ITEM(keys, i);
373 std::string mtCppName = CPyCppyy_PyText_AsString(key);
374 const auto& v = FindBaseMethod(tbase, mtCppName);
375 for (auto idx : v)
377 if (!v.empty()) {
378 if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear();
379 }
380 }
381 Py_DECREF(keys);
382 }
383 }
384 }
386
387// constructors: build up from the argument types of the base class, for use by the Python
388// derived class (inheriting with/ "using" does not work b/c base class constructors may
389// have been deleted),
391
392// for working with C++ templates, additional constructors are needed to make
393// sure the python object is properly carried, but they can only be generated
394// if the base class supports them
395 if (1 < nBases && (!has_ctors || has_default == nBases))
396 code << " " << derivedName << "() {}\n";
397 if (has_cctor == nBases) {
398 code << " " << derivedName << "(const " << derivedName << "& other) : ";
399 for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) {
400 if (ibase != 0) code << ", ";
401 code << base_infos[ibase].bname << "(other)";
402 }
403 if (!isDeepHierarchy)
404 code << ", _internal_self(other._internal_self, this)";
405 code << " {}\n";
406 }
407 if (has_tmpl_ctors && base_infos.size() == 1) {
408 // support for templated ctors in single inheritance (TODO: also multi possible?)
409 code << " template<typename ...Args>\n " << derivedName << "(Args... args) : "
410 << base_infos[0].bname << "(args...) {}\n";
411 }
412
413// destructor: default is fine
414
415// pull in data members that are protected
416 bool setPublic = false;
417 for (const auto& binfo : base_infos) {
419 for (Cppyy::TCppIndex_t idata = 0; idata < nData; ++idata) {
420 if (Cppyy::IsProtectedData(binfo.btype, idata)) {
421 const std::string dm_name = Cppyy::GetDatamemberName(binfo.btype, idata);
422 if (dm_name != "_internal_self") {
423 const std::string& dname = Cppyy::GetDatamemberName(binfo.btype, idata);
424 protected_names.insert(dname);
425 if (!setPublic) {
426 code << "public:\n";
427 setPublic = true;
428 }
429 code << " using " << binfo.bname << "::" << dname << ";\n";
430 }
431 }
432 }
433 }
434
435// initialize the dispatch pointer for all direct bases that have one
436 BaseInfos_t::size_type disp_inited = 0;
437 code << "public:\n static void _init_dispatchptr(" << derivedName << "* inst, PyObject* self) {\n";
438 if (1 < base_infos.size()) {
439 for (const auto& binfo : base_infos) {
440 if (Cppyy::GetDatamemberIndex(binfo.btype, "_internal_self") != (Cppyy::TCppIndex_t)-1) {
441 code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n";
442 disp_inited += 1;
443 }
444 }
445 }
446// The dispatch initializer is only used in constructors, and C++ object start out
447// as owned by C++, with Python ownership explicitly set only later. To match, the
448// dispatch pointer needs to start out with a hard reference, i.e. C++ ownership of
449// the dispatch object. If the constructor has __creates__ set to True (default),
450// then a call to PythonOwns() will switch the hard ref to a weak ref, preventing
451// accidental circular references.
452 if (disp_inited != base_infos.size())
453 code << " new ((void*)&inst->_internal_self) CPyCppyy::DispatchPtr{self, true};\n";
454 code << " }";
455
456// provide an accessor to re-initialize after round-tripping from C++ (internal)
457 code << "\n static PyObject* _get_dispatch(" << derivedName << "* inst) {\n"
458 " PyGILState_STATE state = PyGILState_Ensure();\n"
459 " PyObject* res = (PyObject*)inst->_internal_self;\n"
460 " Py_XINCREF(res);\n"
461 " PyGILState_Release(state);\n"
462 " return res;\n }";
463
464// finish class declaration
465 code << "};\n}";
466
467// finally, compile the code
468 if (!Cppyy::Compile(code.str())) {
469 err << "failed to compile the dispatcher code";
470 return false;
471 }
472
473// keep track internally of the actual C++ type (this is used in
474// CPPConstructor to call the dispatcher's one instead of the base)
475 Cppyy::TCppScope_t disp = Cppyy::GetScope("__cppyy_internal::"+derivedName);
476 if (!disp) {
477 err << "failed to retrieve the internal dispatcher";
478 return false;
479 }
480 klass->fCppType = disp;
481
482// at this point, the dispatcher only lives in C++, as opposed to regular classes
483// that are part of the hierarchy in Python, so create it, which will cache it for
484// later use by e.g. the MemoryRegulator
485 unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross);
487 if (flags) ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross;
488 ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython;
489
490// finally, to expose protected members, copy them over from the C++ dispatcher base
491// to the Python dictionary (the C++ dispatcher's Python proxy is not a base of the
492// Python class to keep the inheritance tree intact)
493 for (const auto& name : protected_names) {
494 PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict);
495#if PY_VERSION_HEX < 0x30d00f0
497#else
498 PyObject* pyf = nullptr;
500#endif
501 if (pyf) {
502 PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf);
503 Py_DECREF(pyf);
504 }
506 }
507
509
510 return true;
511}
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:76
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:81
static void InjectMethod(Cppyy::TCppMethod_t method, const std::string &mtCppName, std::ostringstream &code)
static void build_constructors(const std::string &derivedName, const BaseInfos_t &base_infos, const AllCtors_t &ctors, std::ostringstream &code, const CtorInfos_t &methods=CtorInfos_t{}, size_t idx=0)
_object PyObject
#define b(i)
Definition RSha256.hxx:100
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
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
char name[80]
Definition TGX11.cxx:110
std::string remove_const(const std::string &cppname)
Definition TypeManip.cxx:80
std::string compound(const std::string &name)
PyObject * CreateScopeProxy(Cppyy::TCppScope_t, const unsigned flags=0)
bool CPPScope_Check(T *object)
Definition CPPScope.h:81
bool InsertDispatcher(CPPScope *klass, PyObject *bases, PyObject *dct, std::ostringstream &err)
size_t TCppIndex_t
Definition cpp_cppyy.h:24
RPY_EXPORTED TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace=false)
intptr_t TCppMethod_t
Definition cpp_cppyy.h:22
RPY_EXPORTED TCppIndex_t GetMethodReqArgs(TCppMethod_t)
RPY_EXPORTED std::vector< TCppIndex_t > GetMethodIndicesFromName(TCppScope_t scope, const std::string &name)
RPY_EXPORTED TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace=false)
RPY_EXPORTED std::string GetMethodName(TCppMethod_t)
RPY_EXPORTED bool Compile(const std::string &code, bool silent=false)
RPY_EXPORTED bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsConstructor(TCppMethod_t method)
RPY_EXPORTED TCppIndex_t GetNumMethods(TCppScope_t scope, bool accept_namespace=false)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:19
RPY_EXPORTED TCppIndex_t GetMethodNumArgs(TCppMethod_t)
RPY_EXPORTED std::string GetBaseName(TCppType_t type, TCppIndex_t ibase)
RPY_EXPORTED bool IsNamespace(TCppScope_t scope)
RPY_EXPORTED std::string GetScopedFinalName(TCppType_t type)
RPY_EXPORTED std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg)
RPY_EXPORTED TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string &name)
RPY_EXPORTED TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
RPY_EXPORTED bool HasVirtualDestructor(TCppType_t type)
RPY_EXPORTED bool IsConstMethod(TCppMethod_t)
size_t TCppScope_t
Definition cpp_cppyy.h:18
RPY_EXPORTED bool IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth)
RPY_EXPORTED TCppIndex_t GetNumBases(TCppType_t type)
RPY_EXPORTED std::string GetMethodResultType(TCppMethod_t)
RPY_EXPORTED std::string GetFinalName(TCppType_t type)
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsPublicMethod(TCppMethod_t method)
RPY_EXPORTED bool IsProtectedMethod(TCppMethod_t method)