Skip to content

Commit e84fd21

Browse files
committed
[CPyCppyy] Introduce new public CreatePyValueFromMemory function
Add a CPyCppyy API function to build a Python value from a memory address using a Converter, with the possibility to build also an array proxy with a given shape. This is needed to implement the TTree pythonizations without including private CPyCppyy internals. In principle, the TTree pythonizations can now also be implemented completely in Python, which would be the next step.
1 parent 295249a commit e84fd21

4 files changed

Lines changed: 105 additions & 24 deletions

File tree

bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ CPYCPPYY_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0)
127127
// delete a previously created converter
128128
CPYCPPYY_EXTERN void DestroyConverter(Converter* p);
129129

130+
// Build a Python value from a memory address using a Converter.
131+
//
132+
// If ``ndim`` is non-zero, the value is treated as an array of shape
133+
// ``dims[0] x dims[1] x ... x dims[ndim-1]`` and ``FromMemory(&address)``
134+
// is called on the converter (matching the calling convention array
135+
// converters expect). Otherwise the value is treated as a scalar and
136+
// ``FromMemory(address)`` is called directly.
137+
//
138+
// Returns a new reference on success, or nullptr and sets a Python error if
139+
// no converter for ``typeName`` could be created.
140+
CPYCPPYY_EXTERN PyObject* CreatePyValueFromMemory(
141+
const std::string& typeName, void* address,
142+
dim_t ndim = 0, const dim_t* dims = nullptr);
143+
130144
// register a custom converter
131145
typedef Converter* (*ConverterFactory_t)(cdims_t);
132146
CPYCPPYY_EXTERN bool RegisterConverter(const std::string& name, ConverterFactory_t);

bindings/pyroot/cppyy/CPyCppyy/src/API.cxx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// Bindings
22
#include "CPyCppyy.h"
3+
#include "Dimensions.h"
34
#define CPYCPPYY_INTERNAL 1
45
#include "CPyCppyy/API.h"
56
#undef CPYCPPYY_INTERNAL
@@ -462,3 +463,29 @@ void CPyCppyy::Prompt() {
462463
// enter i/o interactive mode
463464
PyRun_InteractiveLoop(stdin, const_cast<char*>("\0"));
464465
}
466+
467+
//-----------------------------------------------------------------------------
468+
PyObject* CPyCppyy::CreatePyValueFromMemory(
469+
const std::string& typeName, void* address, dim_t ndim, const dim_t* dims)
470+
{
471+
// Build a Python value from a memory address using a Converter. See the
472+
// declaration in CPyCppyy/API.h for the full semantics.
473+
Dimensions shape(ndim, const_cast<dim_t*>(dims));
474+
Converter* cnv = CreateConverter(typeName, shape);
475+
if (!cnv) {
476+
PyErr_Format(PyExc_TypeError,
477+
"no converter available for type '%s'", typeName.c_str());
478+
return nullptr;
479+
}
480+
481+
PyObject* result = nullptr;
482+
if (ndim) {
483+
// Array converter: FromMemory expects the address of the data pointer.
484+
result = cnv->FromMemory(&address);
485+
} else {
486+
// Scalar converter: FromMemory expects the address of the value.
487+
result = cnv->FromMemory(address);
488+
}
489+
DestroyConverter(cnv);
490+
return result;
491+
}

bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,11 @@ namespace CPyCppyy {
263263
static std::map<std::string, std::vector<PyObject*>> pyzMap;
264264
return pyzMap;
265265
}
266+
267+
// Forward-declared from CPyCppyy/API.h (which can't be #include'd here
268+
// because it would conflict with the internal Converters.h).
269+
PyObject* CreatePyValueFromMemory(
270+
const std::string& typeName, void* address, dim_t ndim = 0, const dim_t* dims = nullptr);
266271
}
267272

268273

@@ -816,6 +821,55 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds)
816821
return BindCppObjectNoCast(addr, cast_type);
817822
}
818823

824+
//----------------------------------------------------------------------------
825+
static PyObject* CreateValueFromMemoryPy(PyObject*, PyObject* args)
826+
{
827+
// Python entry point for CPyCppyy::CreatePyValueFromMemory.
828+
//
829+
// Arguments:
830+
// type_name (str): C++ type name to convert from, e.g. "double", "int",
831+
// "double[]", "float*", "MyClass*", etc.
832+
// address (int): memory address to read from. For scalar (non-array)
833+
// types this is the address of the value. For array
834+
// types this is the address of the data array itself
835+
// (i.e. the value of the data pointer).
836+
// dims (sequence, optional): sequence of integers giving the shape of
837+
// the array. If provided, the value is treated as an
838+
// array (a LowLevelView is returned). If omitted or
839+
// None, the value is treated as a scalar.
840+
const char* typeName = nullptr;
841+
PyObject* addressArg = nullptr;
842+
PyObject* dimsArg = Py_None;
843+
if (!PyArg_ParseTuple(args, const_cast<char*>("sO|O:_create_value_from_memory"),
844+
&typeName, &addressArg, &dimsArg))
845+
return nullptr;
846+
847+
void* address = PyLong_AsVoidPtr(addressArg);
848+
if (PyErr_Occurred())
849+
return nullptr;
850+
851+
std::vector<dim_t> dimsVec;
852+
if (dimsArg && dimsArg != Py_None) {
853+
if (!PySequence_Check(dimsArg)) {
854+
PyErr_SetString(PyExc_TypeError, "_create_value_from_memory: dims must be a sequence");
855+
return nullptr;
856+
}
857+
Py_ssize_t ndim = PySequence_Length(dimsArg);
858+
dimsVec.reserve(ndim);
859+
for (Py_ssize_t i = 0; i < ndim; ++i) {
860+
PyObject* item = PySequence_GetItem(dimsArg, i);
861+
if (!item) return nullptr;
862+
dim_t d = (dim_t)PyLong_AsLongLong(item);
863+
Py_DECREF(item);
864+
if (PyErr_Occurred()) return nullptr;
865+
dimsVec.push_back(d);
866+
}
867+
}
868+
869+
return CPyCppyy::CreatePyValueFromMemory(
870+
typeName, address, (dim_t)dimsVec.size(), dimsVec.data());
871+
}
872+
819873
//----------------------------------------------------------------------------
820874
static PyObject* Move(PyObject*, PyObject* pyobject)
821875
{
@@ -996,6 +1050,8 @@ static PyMethodDef gCPyCppyyMethods[] = {
9961050
METH_O, (char*)"Represent an array of objects as raw memory."},
9971051
{(char*)"bind_object", (PyCFunction)BindObject,
9981052
METH_VARARGS | METH_KEYWORDS, (char*) "Create an object of given type, from given address."},
1053+
{(char*)"_create_value_from_memory", (PyCFunction)CreateValueFromMemoryPy,
1054+
METH_VARARGS, (char*) "Build a Python value from a memory address using a Converter."},
9991055
{(char*) "move", (PyCFunction)Move,
10001056
METH_O, (char*)"Cast the C++ object to become movable."},
10011057
{(char*) "add_pythonization", (PyCFunction)AddPythonization,

bindings/pyroot/pythonizations/src/TTreePyz.cxx

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,11 @@
1212
// Bindings
1313
#include <Python.h>
1414

15-
// TODO: refactor public CPyCppyy API such that this forward declaration is not
16-
// needed anymore. Including "CPyCppyy/API.h" should be enough.
17-
namespace CPyCppyy {
18-
typedef Py_ssize_t dim_t;
19-
} // namespace CPyCppyy
15+
#include "CPyCppyy/API.h"
2016

2117
#include "../../cppyy/CPyCppyy/src/Cppyy.h"
2218
#include "../../cppyy/CPyCppyy/src/CPPInstance.h"
2319
#include "../../cppyy/CPyCppyy/src/ProxyWrappers.h"
24-
#include "../../cppyy/CPyCppyy/src/Dimensions.h"
25-
26-
#include "CPyCppyy/API.h"
2720

2821
#include "PyROOTPythonize.h"
2922

@@ -150,30 +143,21 @@ static PyObject *WrapLeaf(TLeaf *leaf)
150143
if (std::count(title.begin(), title.end(), '[') >= 2) {
151144
dimsVec = getMultiDims(title);
152145
}
153-
CPyCppyy::Dimensions dims{static_cast<dim_t>(dimsVec.size()), dimsVec.data()};
154-
Converter *pcnv = CreateConverter(typeName + (isStatic ? "[]" : "*"), dims);
155-
156146
void *address = 0;
157147
if (leaf->GetBranch())
158148
address = (void *)leaf->GetBranch()->GetAddress();
159149
if (!address)
160150
address = (void *)leaf->GetValuePointer();
161151

162-
PyObject *value = pcnv->FromMemory(&address);
163-
CPyCppyy::DestroyConverter(pcnv);
164-
165-
return value;
152+
return CPyCppyy::CreatePyValueFromMemory(
153+
typeName + (isStatic ? "[]" : "*"), address,
154+
static_cast<dim_t>(dimsVec.size()), dimsVec.data());
166155
} else if (leaf->GetValuePointer()) {
167156
// value types
168-
Converter *pcnv = CreateConverter(leaf->GetTypeName());
169-
PyObject *value = 0;
170-
if (leaf->IsA() == TLeafElement::Class() || leaf->IsA() == TLeafObject::Class())
171-
value = pcnv->FromMemory((void *)*(void **)leaf->GetValuePointer());
172-
else
173-
value = pcnv->FromMemory((void *)leaf->GetValuePointer());
174-
CPyCppyy::DestroyConverter(pcnv);
175-
176-
return value;
157+
void *address = (leaf->IsA() == TLeafElement::Class() || leaf->IsA() == TLeafObject::Class())
158+
? (void *)*(void **)leaf->GetValuePointer()
159+
: (void *)leaf->GetValuePointer();
160+
return CPyCppyy::CreatePyValueFromMemory(leaf->GetTypeName(), address);
177161
}
178162

179163
return nullptr;

0 commit comments

Comments
 (0)