Logo ROOT  
Reference Guide
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 <sstream>
12
13
14//----------------------------------------------------------------------------
15static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& mtCppName, std::ostringstream& code)
16{
17// inject implementation for an overridden method
18 using namespace CPyCppyy;
19
20// method declaration
21 std::string retType = Cppyy::GetMethodResultType(method);
22 code << " " << retType << " " << mtCppName << "(";
23
24// build out the signature with predictable formal names
26 std::vector<std::string> argtypes; argtypes.reserve(nArgs);
27 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) {
28 argtypes.push_back(Cppyy::GetMethodArgType(method, i));
29 if (i != 0) code << ", ";
30 code << argtypes.back() << " arg" << i;
31 }
32 code << ") ";
33 if (Cppyy::IsConstMethod(method)) code << "const ";
34 code << "{\n";
35
36// start function body
37 Utility::ConstructCallbackPreamble(retType, argtypes, code);
38
39// perform actual method call
40#if PY_VERSION_HEX < 0x03000000
41 code << " PyObject* mtPyName = PyString_FromString(\"" << mtCppName << "\");\n" // TODO: intern?
42#else
43 code << " PyObject* mtPyName = PyUnicode_FromString(\"" << mtCppName << "\");\n"
44#endif
45 " PyObject* pyresult = PyObject_CallMethodObjArgs((PyObject*)m_self, mtPyName";
46 for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i)
47 code << ", pyargs[" << i << "]";
48 code << ", NULL);\n Py_DECREF(mtPyName);\n";
49
50// close
51 Utility::ConstructCallbackReturn(retType, nArgs, code);
52}
53
54//----------------------------------------------------------------------------
56{
57// Scan all methods in dct and where it overloads base methods in klass, create
58// dispatchers on the C++ side. Then interject the dispatcher class.
59 if (Cppyy::IsNamespace(klass->fCppType) || !PyDict_Check(dct)) {
60 PyErr_Format(PyExc_TypeError,
61 "%s not an acceptable base: is namespace or has no dict", Cppyy::GetScopedFinalName(klass->fCppType).c_str());
62 return false;
63 }
64
66 PyErr_Format(PyExc_TypeError,
67 "%s not an acceptable base: no virtual destructor", Cppyy::GetScopedFinalName(klass->fCppType).c_str());
68 return false;
69 }
70
72 return false;
73
74 const std::string& baseName = TypeManip::template_base(Cppyy::GetFinalName(klass->fCppType));
75 const std::string& baseNameScoped = Cppyy::GetScopedFinalName(klass->fCppType);
76
77// once classes can be extended, should consider re-use; for now, since derived
78// python classes can differ in what they override, simply use different shims
79 static int counter = 0;
80 std::ostringstream osname;
81 osname << "Dispatcher" << ++counter;
82 const std::string& derivedName = osname.str();
83
84// generate proxy class with the relevant method dispatchers
85 std::ostringstream code;
86
87// start class declaration
88 code << "namespace __cppyy_internal {\n"
89 << "class " << derivedName << " : public ::" << baseNameScoped << " {\n"
90 " CPyCppyy::DispatchPtr m_self;\n"
91 "public:\n";
92
93// add a virtual destructor for good measure
94 code << " virtual ~" << derivedName << "() {}\n";
95
96// methods: first collect all callables, then get overrides from base class, for
97// those that are still missing, search the hierarchy
98 PyObject* clbs = PyDict_New();
99 PyObject* items = PyDict_Items(dct);
100 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(items); ++i) {
101 PyObject* value = PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 1);
102 if (PyCallable_Check(value))
103 PyDict_SetItem(clbs, PyTuple_GET_ITEM(PyList_GET_ITEM(items, i), 0), value);
104 }
105 Py_DECREF(items);
106 if (PyDict_DelItem(clbs, PyStrings::gInit) != 0)
107 PyErr_Clear();
108
109// protected methods and data need their access changed in the C++ trampoline and then
110// exposed on the Python side; so, collect their names as we go along
111 std::vector<std::string> protected_names;
112
113// simple case: methods from current class
114 bool has_default = false;
115 bool has_cctor = false;
116 bool has_constructors = false;
117 const Cppyy::TCppIndex_t nMethods = Cppyy::GetNumMethods(klass->fCppType);
118 for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) {
119 Cppyy::TCppMethod_t method = Cppyy::GetMethod(klass->fCppType, imeth);
120
121 if (Cppyy::IsConstructor(method) && (Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method))) {
122 has_constructors = true;
124 if (nreq == 0)
125 has_default = true;
126 else if (!has_cctor && nreq == 1) {
127 const std::string& argtype = Cppyy::GetMethodArgType(method, 0);
128 if (Utility::Compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == baseNameScoped)
129 has_cctor = true;
130 }
131 continue;
132 }
133
134 std::string mtCppName = Cppyy::GetMethodName(method);
135 PyObject* key = CPyCppyy_PyText_FromString(mtCppName.c_str());
136 int contains = PyDict_Contains(dct, key);
137 if (contains == -1) PyErr_Clear();
138 if (contains != 1) {
139 Py_DECREF(key);
140
141 // if the method is protected, we expose it with a 'using'
142 if (Cppyy::IsProtectedMethod(method)) {
143 protected_names.push_back(mtCppName);
144 code << " using " << baseName << "::" << mtCppName << ";\n";
145 }
146
147 continue;
148 }
149
150 InjectMethod(method, mtCppName, code);
151
152 if (PyDict_DelItem(clbs, key) != 0)
153 PyErr_Clear(); // happens for overloads
154 Py_DECREF(key);
155 }
156
157// try to locate left-overs in base classes
158 if (PyDict_Size(clbs)) {
159 size_t nbases = Cppyy::GetNumBases(klass->fCppType);
160 for (size_t ibase = 0; ibase < nbases; ++ibase) {
162 Cppyy::GetBaseName(klass->fCppType, ibase));
163
164 PyObject* keys = PyDict_Keys(clbs);
165 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) {
166 // TODO: should probably invert this looping; but that makes handling overloads clunky
167 PyObject* key = PyList_GET_ITEM(keys, i);
168 std::string mtCppName = CPyCppyy_PyText_AsString(key);
169 const auto& v = Cppyy::GetMethodIndicesFromName(tbase, mtCppName);
170 for (auto idx : v)
171 InjectMethod(Cppyy::GetMethod(tbase, idx), mtCppName, code);
172 if (!v.empty()) {
173 if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear();
174 }
175 }
176 Py_DECREF(keys);
177 }
178 }
179 Py_DECREF(clbs);
180
181// constructors: most are simply inherited, for use by the Python derived class
182 code << " using " << baseName << "::" << baseName << ";\n";
183
184// for working with C++ templates, additional constructors are needed to make
185// sure the python object is properly carried, but they can only be generated
186// if the base class supports them
187 if (has_default || !has_constructors)
188 code << " " << derivedName << "() {}\n";
189 if (has_default || has_cctor || !has_constructors) {
190 code << " " << derivedName << "(const " << derivedName << "& other) : ";
191 if (has_cctor)
192 code << baseName << "(other), ";
193 code << "m_self(other.m_self, this) {}\n";
194 }
195
196// destructor: default is fine
197
198// pull in data members that are protected
200 if (nData) code << "public:\n";
201 for (Cppyy::TCppIndex_t idata = 0; idata < nData; ++idata) {
202 if (Cppyy::IsProtectedData(klass->fCppType, idata)) {
203 protected_names.push_back(Cppyy::GetDatamemberName(klass->fCppType, idata));
204 code << " using " << baseName << "::" << protected_names.back() << ";\n";
205 }
206 }
207
208// add an offset calculator for the dispatch ptr as needed
209 code << "public:\n"
210 << "static size_t _dispatchptr_offset() { return (size_t)&(("
211 << derivedName << "*)(0x0))->m_self; }";
212
213// finish class declaration
214 code << "};\n}";
215
216// finally, compile the code
217 if (!Cppyy::Compile(code.str()))
218 return false;
219
220// keep track internally of the actual C++ type (this is used in
221// CPPConstructor to call the dispatcher's one instead of the base)
222 Cppyy::TCppScope_t disp = Cppyy::GetScope("__cppyy_internal::"+derivedName);
223 if (!disp) return false;
224 klass->fCppType = disp;
225
226// at this point, the dispatcher only lives in C++, as opposed to regular classes
227// that are part of the hierarchy in Python, so create it, which will cache it for
228// later use by e.g. the MemoryRegulator
229 PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp);
230
231// finally, to expose protected members, copy them over from the C++ dispatcher base
232// to the Python dictionary (the C++ dispatcher's Python proxy is not a base of the
233// Python class to keep the inheritance tree intact)
234 for (const auto& name : protected_names) {
235 PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict);
236 PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str());
237 if (pyf) {
238 PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf);
239 Py_DECREF(pyf);
240 }
241 Py_DECREF(disp_dct);
242 }
243
244 Py_XDECREF(disp_proxy);
245
246 return true;
247}
#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)
Definition: Dispatcher.cxx:15
_object PyObject
Definition: PyMethodBase.h:41
char name[80]
Definition: TGX11.cxx:109
Cppyy::TCppType_t fCppType
Definition: CPPScope.h:50
PyObject * gDict
Definition: PyStrings.cxx:14
PyObject * gInit
Definition: PyStrings.cxx:20
std::string template_base(const std::string &cppname)
Definition: TypeManip.cxx:124
std::string clean_type(const std::string &cppname, bool template_strip=true, bool const_strip=true)
Definition: TypeManip.cxx:98
void ConstructCallbackPreamble(const std::string &retType, const std::vector< std::string > &argtypes, std::ostringstream &code)
Definition: Utility.cxx:517
void ConstructCallbackReturn(const std::string &retType, int nArgs, std::ostringstream &code)
Definition: Utility.cxx:561
const std::string Compound(const std::string &name)
Definition: Utility.cxx:782
bool IncludePython()
Definition: Utility.cxx:935
bool InsertDispatcher(CPPScope *klass, PyObject *dct)
Definition: Dispatcher.cxx:55
PyObject * CreateScopeProxy(Cppyy::TCppScope_t)
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)
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)