A student
A student

Reputation: 180

Build a PyObject* with a C Object pointer

Say I have this struct:

typedef struct
{
  PyObject_HEAD
  Foo* myFoo;
} PyFoo;

Let's just say that Foo is:

class Foo
{
public:
  hello()
  {
    std::cout << "Hello\n";
  }
};

I don't want to remake class Foo as a python module because it represents a class from a library with a lot more functions and variables (but that isn't relevant to this question). I didn't really understand from the docs how to create a PyObject* in C/C++ with arguments, much less how to do it with C/C++ pointers as arguments.

I am going off this guide: https://docs.python.org/3/extending/newtypes_tutorial.html

I do have my dealloc, new, and init methods from the guide, but I have not tried to initialize and deallocate any values, except for the instance of the object itself.

This question is similar to Build a PyObject* from a C function? but I want to pass an object pointer instead of a function. I am using the same method as Create an object using Python's C API to create the object, but I don't know how I can give an instance of foo to the PyObject.

Upvotes: 0

Views: 2356

Answers (2)

DavidW
DavidW

Reputation: 30888

I think you're making things more complicated than needed trying to call the constructor with a pointer. Your tp_new and tp_init methods are designed to provide a Python interface to making an object instance. If it doesn't make sense to provide a Python interface (for example, if your object must always be created with a C++ pointer) then simply don't provide them - set them to NULL and your object will not be creatable from Python.

In C++ you are not restricted to this interface. You can define your own "C++ factory function" taking whatever arguments you like:

PyFoo* make_PyFoo(Foo* myfoo) {

First allocate your object:

PyFoo *obj = (PyFoo*)(type->tp_alloc(type, 0));
# or
PyFoo *obj = PyObject_New(PyFoo, type); # use PyObject_GC_new if it has cyclic references

The two approaches are pretty much equivalent if you haven't defined a custom allocator. Some error-checking has been omitted here....

Next you can simply use your existing Foo* pointer to initialize the relevant field:

obj->myfoo = myfoo;

Then just return obj (and close the bracket).


This answer was inspired largely by my long-standing dislike of Python capsules. It's very rare to see a sensible use-case for them, but people do like using them anyway.

Upvotes: 2

A student
A student

Reputation: 180

So you can do this using capsules.

When you create the object:

Foo* myFoo = new Foo();
PyObject* capsule = PyCapsule_New((void*) myFoo, "Foo", NULL);
PyObject* argList = Py_BuildValue("(O)", capsule);
PyObject *obj = PyObject_CallObject((PyObject*) &Foo_PyTypeObject, argList);
Py_DECREF(arglist);
Py_DECREF(capsule); // I don't need python to keep the reference to myFoo

Foo_PyTypeObject should be the PyTypeObject that you make for your extension object.

I then used the capsule in my 'new' function.

Foo_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
  Foo* self;
  self = (Foo*) type->tp_alloc(type, 0);
  if (self != NULL)
  {
    static char *kwlist[] = {const_cast<char*>("Foo"), NULL};

    PyObject* capsule = NULL;
    PyArg_ParseTupleAndKeywords(args, kwds, "O", kwlist, &capsule);
    self->myFoo = (Foo*) PyCapsule_GetPointer(capsule, "Foo"); // this is the myFoo in the PyFoo struct
    Py_DECREF(capsule);
  }
  return (PyObject*) self;
}

Upvotes: 0

Related Questions