Skip to content

Commit 30eb771

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 f8c9e03 commit 30eb771

5 files changed

Lines changed: 103 additions & 25 deletions

File tree

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,19 @@ 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*
141+
CreatePyValueFromMemory(const std::string& typeName, void* address, dim_t ndim = 0, const dim_t* dims = nullptr);
142+
130143
// register a custom converter
131144
typedef Converter* (*ConverterFactory_t)(cdims_t);
132145
CPYCPPYY_EXTERN bool RegisterConverter(const std::string& name, ConverterFactory_t);

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

Lines changed: 26 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,28 @@ 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, dims);
474+
Converter* cnv = CreateConverter(typeName, shape);
475+
if (!cnv) {
476+
PyErr_Format(PyExc_TypeError, "no converter available for type '%s'", typeName.c_str());
477+
return nullptr;
478+
}
479+
480+
PyObject* result = nullptr;
481+
if (ndim) {
482+
// Array converter: FromMemory expects the address of the data pointer.
483+
result = cnv->FromMemory(&address);
484+
} else {
485+
// Scalar converter: FromMemory expects the address of the value.
486+
result = cnv->FromMemory(address);
487+
}
488+
DestroyConverter(cnv);
489+
return result;
490+
}

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

Lines changed: 55 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,54 @@ 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, "sO|O:_create_value_from_memory", &typeName, &addressArg, &dimsArg))
844+
return nullptr;
845+
846+
void* address = PyLong_AsVoidPtr(addressArg);
847+
if (PyErr_Occurred())
848+
return nullptr;
849+
850+
std::vector<dim_t> dimsVec;
851+
if (dimsArg && dimsArg != Py_None) {
852+
if (!PySequence_Check(dimsArg)) {
853+
PyErr_SetString(PyExc_TypeError, "_create_value_from_memory: dims must be a sequence");
854+
return nullptr;
855+
}
856+
Py_ssize_t ndim = PySequence_Length(dimsArg);
857+
dimsVec.reserve(ndim);
858+
for (Py_ssize_t i = 0; i < ndim; ++i) {
859+
PyObject* item = PySequence_GetItem(dimsArg, i);
860+
if (!item) return nullptr;
861+
dim_t d = (dim_t)PyLong_AsLongLong(item);
862+
Py_DECREF(item);
863+
if (PyErr_Occurred()) return nullptr;
864+
dimsVec.push_back(d);
865+
}
866+
}
867+
868+
return CPyCppyy::CreatePyValueFromMemory(
869+
typeName, address, (dim_t)dimsVec.size(), dimsVec.data());
870+
}
871+
819872
//----------------------------------------------------------------------------
820873
static PyObject* Move(PyObject*, PyObject* pyobject)
821874
{
@@ -996,6 +1049,8 @@ static PyMethodDef gCPyCppyyMethods[] = {
9961049
METH_O, (char*)"Represent an array of objects as raw memory."},
9971050
{(char*)"bind_object", (PyCFunction)BindObject,
9981051
METH_VARARGS | METH_KEYWORDS, (char*) "Create an object of given type, from given address."},
1052+
{(char*)"_create_value_from_memory", (PyCFunction)CreateValueFromMemoryPy,
1053+
METH_VARARGS, (char*) "Build a Python value from a memory address using a Converter."},
9991054
{(char*) "move", (PyCFunction)Move,
10001055
METH_O, (char*)"Cast the C++ object to become movable."},
10011056
{(char*) "add_pythonization", (PyCFunction)AddPythonization,

bindings/pyroot/cppyy/CPyCppyy/src/Dimensions.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class CPYCPPYY_CLASS_EXPORT Dimensions {
1717
dim_t* fDims;
1818

1919
public:
20-
Dimensions(dim_t ndim = 0, dim_t* dims = nullptr) : fDims(nullptr) {
20+
Dimensions(dim_t ndim = 0, const dim_t* dims = nullptr) : fDims(nullptr) {
2121
if (ndim && ndim != UNKNOWN_SIZE) {
2222
fDims = new dim_t[ndim+1];
2323
fDims[0] = ndim;

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)