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
22 std::string retType = Cppyy::GetMethodResultType(method);
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) {
29 argtypes.push_back(Cppyy::GetMethodArgType(method, 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// start function body
38 Utility::ConstructCallbackPreamble(retType, argtypes, code);
39
40// perform actual method call
41#if PY_VERSION_HEX < 0x03000000
42 code << " PyObject* mtPyName = PyString_FromString(\"" << mtCppName << "\");\n" // TODO: intern?
43#else
44 code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName << "\");\n"
45#endif
46 " PyObject* pyresult = PyObject_CallMethodObjArgs((PyObject*)_internal_self, mtPyName";
47
48 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i)
49 code << ", pyargs[" << i << "]";
50 code << ", NULL);\n Py_DECREF(mtPyName);\n";
51
52// close
53 Utility::ConstructCallbackReturn(retType, nArgs, code);
54}
55
56//----------------------------------------------------------------------------
57namespace {
58 struct BaseInfo {
59 BaseInfo(Cppyy::TCppType_t t, std::string&& bn, std::string&& bns) :
60 btype(t), bname(bn), bname_scoped(bns) {}
62 std::string bname;
63 std::string bname_scoped;
64 };
65
66 typedef std::vector<BaseInfo> BaseInfos_t;
67} // unnamed namespace
68
69//----------------------------------------------------------------------------
70bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, std::ostringstream& err)
71{
72// Scan all methods in dct and where it overloads base methods in klass, create
73// dispatchers on the C++ side. Then interject the dispatcher class.
74 if (!PyTuple_Check(bases) || !PyTuple_GET_SIZE(bases) || !dct || !PyDict_Check(dct)) {
75 err << "internal error: expected tuple of bases and proper dictionary";
76 return false;
77 }
78
79 if (!Utility::IncludePython()) {
80 err << "failed to include Python.h";
81 return false;
82 }
83
84 const Py_ssize_t nBases = PyTuple_GET_SIZE(bases);
85 BaseInfos_t base_infos; base_infos.reserve(nBases);
86 for (Py_ssize_t ibase = 0; ibase < nBases; ++ibase) {
87 auto currentBase = PyTuple_GET_ITEM(bases, ibase);
88 if (!CPPScope_Check(currentBase))
89 // Python base class
90 continue;
91
92 Cppyy::TCppType_t basetype = ((CPPScope*)currentBase)->fCppType;
93 if (!basetype) {
94 err << "base class is incomplete";
95 break;
96 }
97
98 if (Cppyy::IsNamespace(basetype)) {
99 err << Cppyy::GetScopedFinalName(basetype) << " is a namespace";
100 break;
101 }
102
103 if (!Cppyy::HasVirtualDestructor(basetype)) {
104 const std::string& bname = Cppyy::GetScopedFinalName(basetype);
105 PyErr_Warn(PyExc_RuntimeWarning, (char*)("class \""+bname+"\" has no virtual destructor").c_str());
106 }
107
108 base_infos.emplace_back(
109 basetype, TypeManip::template_base(Cppyy::GetFinalName(basetype)), Cppyy::GetScopedFinalName(basetype));
110 }
111
112 if ((Py_ssize_t)base_infos.size() == 0)
113 return false;
114 else if ((Py_ssize_t)base_infos.size() > 1) {
115 err << "multi cross-inheritance not supported";
116 return false;
117 }
118
119 const auto& binfo = base_infos[0];
120 const Cppyy::TCppType_t baseType = binfo.btype;
121 const std::string& baseName = binfo.bname;
122 const std::string& baseNameScoped = binfo.bname_scoped;
123
124 bool isDeepHierarchy = klass->fCppType && baseType != klass->fCppType;
125
126// once classes can be extended, should consider re-use; for now, since derived
127// python classes can differ in what they override, simply use different shims
128 static int counter = 0;
129 std::ostringstream osname;
130 osname << "Dispatcher" << ++counter;
131 const std::string& derivedName = osname.str();
132
133// generate proxy class with the relevant method dispatchers
134 std::ostringstream code;
135
136// start class declaration
137 code << "namespace __cppyy_internal {\n"
138 << "class " << derivedName << " : public ::" << baseNameScoped << " {\n";
139 if (!isDeepHierarchy)
140 code << "protected:\n CPyCppyy::DispatchPtr _internal_self;\n";
141 code << "public:\n";
142
143// add a virtual destructor for good measure
144 code << " virtual ~" << derivedName << "() {}\n";
145
146// methods: first collect all callables, then get overrides from base class, for
147// those that are still missing, search the hierarchy
148 PyObject* clbs = PyDict_New();
149 PyObject* items = PyDict_Items(dct);
150 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) {
151 PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1);
152 if (PyCallable_Check(value))
153 PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), value);
154 }
155 Py_DECREF(items);
156 if (PyDict_DelItem(clbs, PyStrings::gInit) != 0)
157 PyErr_Clear();
158
159// protected methods and data need their access changed in the C++ trampoline and then
160// exposed on the Python side; so, collect their names as we go along
161 std::set<std::string> protected_names;
162
163// simple case: methods from current class
164 bool has_default = false;
165 bool has_cctor = false;
166 bool has_constructors = false;
167 const Cppyy::TCppIndex_t nMethods = Cppyy::GetNumMethods(baseType);
168 for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) {
169 Cppyy::TCppMethod_t method = Cppyy::GetMethod(baseType, imeth);
170
171 if (Cppyy::IsConstructor(method) && (Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method))) {
172 has_constructors = true;
174 if (nreq == 0)
175 has_default = true;
176 else if (!has_cctor && nreq == 1) {
177 const std::string& argtype = Cppyy::GetMethodArgType(method, 0);
178 if (Utility::Compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == baseNameScoped)
179 has_cctor = true;
180 }
181 continue;
182 }
183
184 std::string mtCppName = Cppyy::GetMethodName(method);
185 PyObject* key = CPyCppyy_PyText_FromString(mtCppName.c_str());
186 int contains = PyDict_Contains(dct, key);
187 if (contains == -1) PyErr_Clear();
188 if (contains != 1) {
189 Py_DECREF(key);
190
191 // if the method is protected, we expose it through re-declaration and forwarding (using
192 // does not work here b/c there may be private overloads)
193 if (Cppyy::IsProtectedMethod(method)) {
194 protected_names.insert(mtCppName);
195
196 code << " " << Cppyy::GetMethodResultType(method) << " " << mtCppName << "(";
198 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
199 if (i != 0) code << ", ";
200 code << Cppyy::GetMethodArgType(method, i) << " arg" << i;
201 }
202 code << ") ";
203 if (Cppyy::IsConstMethod(method)) code << "const ";
204 code << "{\n return " << baseName << "::" << mtCppName << "(";
205 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
206 if (i != 0) code << ", ";
207 code << "arg" << i;
208 }
209 code << ");\n }\n";
210 }
211
212 continue;
213 }
214
215 InjectMethod(method, mtCppName, code);
216
217 if (PyDict_DelItem(clbs, key) != 0)
218 PyErr_Clear(); // happens for overloads
219 Py_DECREF(key);
220 }
221
222// try to locate left-overs in base classes
223 if (PyDict_Size(clbs)) {
224 size_t nbases = Cppyy::GetNumBases(baseType);
225 for (size_t ibase = 0; ibase < nbases; ++ibase) {
227 Cppyy::GetBaseName(baseType, ibase));
228
229 PyObject* keys = PyDict_Keys(clbs);
230 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) {
231 // TODO: should probably invert this looping; but that makes handling overloads clunky
232 PyObject* key = PyList_GET_ITEM(keys, i);
233 std::string mtCppName = CPyCppyy_PyText_AsString(key);
234 const auto& v = Cppyy::GetMethodIndicesFromName(tbase, mtCppName);
235 for (auto idx : v)
236 InjectMethod(Cppyy::GetMethod(tbase, idx), mtCppName, code);
237 if (!v.empty()) {
238 if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear();
239 }
240 }
241 Py_DECREF(keys);
242 }
243 }
244 Py_DECREF(clbs);
245
246// constructors: most are simply inherited, for use by the Python derived class
247 code << " using " << baseName << "::" << baseName << ";\n";
248
249// for working with C++ templates, additional constructors are needed to make
250// sure the python object is properly carried, but they can only be generated
251// if the base class supports them
252 if (has_default || !has_constructors)
253 code << " " << derivedName << "() {}\n";
254 if (has_default || has_cctor || !has_constructors) {
255 code << " " << derivedName << "(const " << derivedName << "& other)";
256 if (has_cctor)
257 code << " : " << baseName << "(other)";
258 if (!isDeepHierarchy) {
259 if (has_cctor) code << ", ";
260 else code << " : ";
261 code << "_internal_self(other._internal_self, this)";
262 }
263 code << " {}\n";
264 }
265
266// destructor: default is fine
267
268// pull in data members that are protected
270 if (nData) code << "public:\n";
271 for (Cppyy::TCppIndex_t idata = 0; idata < nData; ++idata) {
272 if (Cppyy::IsProtectedData(baseType, idata)) {
273 const std::string dm_name = Cppyy::GetDatamemberName(baseType, idata);
274 if (dm_name != "_internal_self") {
275 protected_names.insert(dm_name);
276 code << " using " << baseName << "::" << dm_name << ";\n";
277 }
278 }
279 }
280
281// initialize the dispatch pointer for base
282 code << "public:\n static void _init_dispatchptr(" << derivedName << "* inst, PyObject* self) {\n"
283 " new ((void*)&inst->_internal_self) CPyCppyy::DispatchPtr{self};\n"
284 " }\n";
285
286// finish class declaration
287 code << "};\n}";
288
289// finally, compile the code
290 if (!Cppyy::Compile(code.str())) {
291 err << "failed to compile the dispatcher code";
292 return false;
293 }
294
295// keep track internally of the actual C++ type (this is used in
296// CPPConstructor to call the dispatcher's one instead of the base)
297 Cppyy::TCppScope_t disp = Cppyy::GetScope("__cppyy_internal::"+derivedName);
298 if (!disp) {
299 err << "failed to retrieve the internal dispatcher";
300 return false;
301 }
302 klass->fCppType = disp;
303
304// at this point, the dispatcher only lives in C++, as opposed to regular classes
305// that are part of the hierarchy in Python, so create it, which will cache it for
306// later use by e.g. the MemoryRegulator
307 PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp);
308
309// finally, to expose protected members, copy them over from the C++ dispatcher base
310// to the Python dictionary (the C++ dispatcher's Python proxy is not a base of the
311// Python class to keep the inheritance tree intact)
312 for (const auto& name : protected_names) {
313 PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict);
314 PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str());
315 if (pyf) {
316 PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf);
317 Py_DECREF(pyf);
318 }
319 Py_DECREF(disp_dct);
320 }
321
322 Py_XDECREF(disp_proxy);
323
324 return true;
325}
int Py_ssize_t
Definition CPyCppyy.h:236
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:97
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:102
static void InjectMethod(Cppyy::TCppMethod_t method, const std::string &mtCppName, std::ostringstream &code)
_object PyObject
char name[80]
Definition TGX11.cxx:110
Cppyy::TCppType_t fCppType
Definition CPPScope.h:50
Set of helper functions that are invoked from the pythonizors, on the Python side.
bool CPPScope_Check(T *object)
Definition CPPScope.h:76
PyObject * CreateScopeProxy(Cppyy::TCppScope_t)
bool InsertDispatcher(CPPScope *klass, PyObject *bases, PyObject *dct, std::ostringstream &err)
size_t TCppIndex_t
Definition cpp_cppyy.h:24
RPY_EXPORTED std::vector< TCppIndex_t > GetMethodIndicesFromName(TCppScope_t scope, const std::string &name)
RPY_EXPORTED bool Compile(const std::string &code)
intptr_t TCppMethod_t
Definition cpp_cppyy.h:22
RPY_EXPORTED bool IsProtectedMethod(TCppMethod_t method)
TCppScope_t TCppType_t
Definition cpp_cppyy.h:19
RPY_EXPORTED bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata)
RPY_EXPORTED bool IsConstructor(TCppMethod_t method)
RPY_EXPORTED TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth)
RPY_EXPORTED std::string GetFinalName(TCppType_t type)
RPY_EXPORTED bool IsPublicMethod(TCppMethod_t method)
RPY_EXPORTED TCppIndex_t GetMethodReqArgs(TCppMethod_t)
RPY_EXPORTED std::string GetMethodName(TCppMethod_t)
RPY_EXPORTED TCppIndex_t GetNumBases(TCppType_t type)
RPY_EXPORTED bool IsConstMethod(TCppMethod_t)
size_t TCppScope_t
Definition cpp_cppyy.h:18
RPY_EXPORTED std::string GetBaseName(TCppType_t type, TCppIndex_t ibase)
RPY_EXPORTED TCppIndex_t GetMethodNumArgs(TCppMethod_t)
RPY_EXPORTED std::string GetScopedFinalName(TCppType_t type)
RPY_EXPORTED bool IsNamespace(TCppScope_t scope)
RPY_EXPORTED TCppIndex_t GetNumDatamembers(TCppScope_t scope)
RPY_EXPORTED TCppScope_t GetScope(const std::string &scope_name)
RPY_EXPORTED std::string GetMethodResultType(TCppMethod_t)
RPY_EXPORTED bool HasVirtualDestructor(TCppType_t type)
RPY_EXPORTED std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg)
RPY_EXPORTED TCppIndex_t GetNumMethods(TCppScope_t scope)