Construct PyROOT object from c_void_p pointer

Dear experts,

Is it possible to construct an instance of a standard ROOT class in PyROOT starting from a raw pointer? I’m writing a Python wrapper for a C++ library using module ctypes. With a simple example, in the C interface I have a code like

extern "C" {
    void *makeHist()
    {
        return new TH1D("hist", "", 1, 0., 1.);
    }
}

and then on the Python side I’d like to do something like

lib = ctypes.cdll.LoadLibrary('<path>')
lib.makeHist.argtypes = []
lib.makeHist.restype = ctypes.c_void_p

def make_hist():
    return ROOT.TH1D(lib.makeHist())

But, of course, PyROOT cannot construct a TH1D from c_void_p.

Hi,

let me try to give to the problem a different spin, just to understand better: why can’t you just do something like:

ROOT.gInterpreter.Declare('''
    TH1D *makeHist()
    {
        return new TH1D("hist", "", 1, 0., 1.);
    }
''')
h = ROOT.makeHist()

?

Cheers,
D

@Danilo, this could be an option. To be more specific, I could parse all relevant headers from the library and link against it with

ROOT.gInterpreter.AddIncludePath('...')
ROOT.gInterpreter.Declare('#include <...>')
ROOT.gSystem.Load('lib.so')

A couple of drawbacks is that this way I wouldn’t have control on what parts of the library are exposed and the library couldn’t use a newer version of the C++ standard than the one with which ROOT had been built.

Anyway, I’m just curious whether the lower-level approach I outlined in the first post is possible. I thought that PyROOT might be doing something like that under the hood.

Hi,

what about jitting a converter?

Best,
Danilo

the low-level functionality you’re (perhaps) after is:

// void* to object proxy conversion, returns a new reference
   static PyObject* ObjectProxy_FromVoidPtr(
      void* addr, const char* classname, Bool_t python_owns = kFALSE );

Do you mean something like this?

ROOT.gInterpreter.Declare('''
    TH1 *convertHist(void *p)
    {
        return static_cast<TH1 *>(p);
    }
''')

def make_hist():
    return ROOT.convertHist(lib.makeHist())

I guess this would do the job… I also take it that there isn’t any standard tool in PyROOT to construct an object from a raw pointer.

Thanks for sharing the ideas.

From signature it looks exactly what I had in mind, but when I try to use it from Python, I get a segfault. Will probably try to play with it more later.

There is: it’s called BindObject. Works just fine:

import ctypes
lib = ctypes.cdll.LoadLibrary('./lib.so')
lib.makeHist.argtypes = []
lib.makeHist.restype = ctypes.c_void_p

ch = lib.makeHist()

import ROOT
cpph = ROOT.BindObject(ch, ROOT.TH1D)

As for crashes, not sure how you can get any: neither call interprets the address in any way. Do you have a stack trace?

1 Like

Thank you @wlav. ROOT.BindObject does the job for me. Do you know if the ownership of the object is transferred to Python? I’m not sure I fully understand the code of BindCppObjectNoCast, which seems to be called under the hood.

I suspect I did something stupid there. I tried with

ROOT.TPython.ObjectProxy_FromVoidPtr(ch, 'TH1D')

but this gives a TypeError because the first argument cannot be converted. So I tried feeding ctypes.c_void_p(ch) instead, which resulted in the crash. Here is the stack trace:

===========================================================
#0  0x00000036e78ac82e in waitpid () from /lib64/libc.so.6
#1  0x00000036e783e479 in do_system () from /lib64/libc.so.6
#2  0x00007fdeae532431 in TUnixSystem::StackTrace() () at /gridsoft/ipnls/root/v6.10.04_source/core/unix/src/TUnixSystem.cxx:2118
#3  0x00007fdeae53480c in TUnixSystem::DispatchSignals(ESignals) () at /gridsoft/ipnls/root/v6.10.04_source/core/unix/src/TUnixSystem.cxx:3643
#4  <signal handler called>
#5  0x00007fdeada83498 in vtable for TAxis () from /gridsoft/ipnls/root/v6.10.04/lib/libHist.so
#6  0x00007fdeb561f027 in ?? ()
#7  0x00007fdeb561f010 in ?? ()
#8  0x0000000000000000 in ?? ()
===========================================================

PyROOT has no way to check ownership, but it won’t own that object. You can use ROOT.SetOwnership(cpph, True) to set it, though (in cppyy master, this is all an attribute on the object: __python_owns__, so it can be both read and written).

Yes, ‘ctypes.c_void_p(ch)’ will not work: the address is not extracted in the converter. Instead, the python object is passed through the void*.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.