Convert ROOT class to PyObject

I am trying to embed python in my C++ application. I need to pass a c++ object to python. Specifically, I would like to be able to call a method on a python object and pass a c++ object as the argument. Clearly PyROOT can take an object of any class for which it has a dictionary and pass that to python as a PyObject. What class/function do I need to use in my c++ code to accomplish the same thing. I noticed that many of the headers that seem to deal with this sort of thing are not installed to the ROOT include directory but are present in the build directory. Should I be using some of the classes from those headers, such as TCppObjectConverter from Converters.h? What libraries do I need to link against to use those classes? If anyone has a simple example that would be very helpful.

Hi,

look in TPython.h, which is a public interface. You want “ObjectProxy_FromVoidPtr”. Of course, for non-dictionary bound C++ classes, you can use the straight Python C-API.

Cheers,
Wim

Wim,

As you say, this code will do what I want. Thank you. I still have one issue though. That is, how can I get the name of the class needed for TPython::ObjectProxy_FromVoidPtr. Right now, I am trying to use template classes which do not have ClassDef or ClassImp but do have dictionaries. It seems that PyROOT is able to determine the types of these objects without the Class() method being present, as it always creates the correct type when I return one of these objects from a function. Also, if I know the correct class string, the TPython::ObjectProxy_FromVoidPtr method also works correctly and gives the correct python object type.

So to clarify:

const char* myClassName = "some_namespace::SomeClass<double>"
auto pvobj = TPython::ObjectProxy_FromVoidPtr((void*)myObject,myClassName); // This works

My question is, how does PyROOT figure out the value of myClassName when no Class() member exists in myObject? I know it can do this because PyROOT works with these classes (even though it is not explicitly calling TPython). I also know that the string “some_namespace::SomeClass” has some meaning to ROOT even in compiled code because I can get the above example working. This leads me to believe that the correct value for myClassName should be discoverable somehow.

I would like to be able to write a single (template) function to wrap any C++ object with a ROOT dictionary up in a PyObject. Can you point me in the right direction?

Thank you
John

John,

not sure I completely follow you …

For returns from functions, PyROOT has the function definition, and therefore the return type (ROOT/meta provides this, from Cling). Starting with nothing but a void* does not work. What can be done, if the void* points to a known class, you can move up or down the inheritance hierarchy.

So, for this:[quote=“jsparger”]I would like to be able to write a single (template) function to wrap any C++ object with a ROOT dictionary up in a PyObject. Can you point me in the right direction?[/quote]you’ll always need one more thing beyond the void*. E.g. an IsA(), a common base class (like TObject), or the dictionary.

Cheers,
Wim

Hi Wim,

I managed to get this working with the code posted below. The function get_python_proxy returns a python proxy for any object. This requires only that a dictionary be built for the type T sent to get_python_proxy and even works for template dictionary’s because of root magic. By that I mean you don’t need an explicit dictionary for the specialized template. So for a class like MyClass, a LinkDef line like:

#pragma link C++ class MyClass+

is good enough to generate the correct Python object for all T (assuming T also has a dictionary). I suppose ROOT creates the dictionary based on the string at runtime. Probably this trick can be used to get around a lot of root dictionary hassles with templates.

Maybe this can help someone else down the line.

John

#include <cxxabi.h>
#include <typeinfo>
#include "TPython.h"
#include "Python.h"

struct Demangler
{
	// from http://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
	static std::string demangle(const char* name)
	{
	    int status = -4; // some arbitrary value to eliminate the compiler warning

	    // enable c++11 by passing the flag -std=c++11 to g++
	    std::unique_ptr<char, void(*)(void*)> res {
	        abi::__cxa_demangle(name, NULL, NULL, &status),
	        std::free
	    };

	    return (status==0) ? res.get() : name ;
	}
};

template <typename T>
PyObject* get_python_proxy(T& pv)
{
	std::string klassName = Demangler::demangle(typeid(pv).name());
	return TPython::ObjectProxy_FromVoidPtr((void*)&pv, klassName.c_str());
}

John,

cool! I should have thought of that. One more small thing: the class also needs to have a vtable (i.e. at least one virtual method), otherwise typeid() won’t work. And you can use TClass::GetClass(typeid()), so that you don’t have to do the demangling yourself (and benefit from the automatic class loading, although PyROOT will that do for you otherwise).

Cheers,
Wim

Wim,

That’s great. I didn’t know that TClass had a method like that. I had been seen ClassInfo_t several places, but I didn’t realize it was std::type_info. I also didn’t know this would trigger generation of the dictionary. You are right that is better than doing the demangling myself.

Do you know how portable the ROOT demangling of type_info is? I don’t know much about name mangling.

Also, good note about the vtable. I had forgotten that was a requirement for typeid.

This reflection and runtime compilation of templates is honestly the coolest thing about ROOT. I get really excited every time I end up messing with it. Thanks to whoever wrote this magic.

Thanks again for the help Wim
John.

John,

as portable as ROOT itself, as the compiler (i.e. cling) provides it. If you look for DemangleName() in TClassEdit.h, you’ll see that it, too, uses the abi::__cxa_demangle method on most platforms. On Windows, it uses an internal method from cling that I’m not familiar with.

Cheers,
Wim