I’m learning the C API to embed Python in a game engine. I’ve got my little family of standard GUI objects, some of which are instantiated in the compiled project, and I’d like to expose both the types and instances to the REPL and scripts.
I’ve got my Python class defined:
static PyTypeObject PyTextureType = {
.tp_name = "mcrfpy.Texture",
.tp_basicsize = sizeof(PyTextureObject),
.tp_itemsize = 0,
.tp_repr = PyTexture::repr,
.tp_hash = PyTexture::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Texture Object"),
.tp_init = (initproc)PyTexture::init,
.tp_new = PyType_GenericNew,
};
The methods pointed to in the PyTypeObject above are all defaults, or static methods in a C++ PyTexture
class.
This works just fine from the REPL: I can call mcrfpy.Texture
, provide arguments for init, and I get a fully functional Python object back. I’m having no issues with using the struct directly from C++.
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # works fine
The other direction - instantiating the C++ object and generating a Python instance had some issues. The object mostly behaves normally, but segfaults under some conditions:
procedure
What steps am I missing or doing incorrectly?
step 1. allocate.
PyObject* obj = PyType_GenericAlloc(&PyTextureType, 0);
I’ve had better luck with GenericAlloc. I originally called PyObject* obj = (PyObject*)PyTextureType.tp_alloc(&PyTextureType, 0);
- this segfaults on the call to tp_alloc
.
step 2. initialize the object; instead of calling init, I directly set the members of the PyTextureObject
struct.
step 3. Add the objects to the module so they can be used from Python.
PyObject* PyInit_mcrfpy()
{
PyObject* m = PyModule_Create(&mcrfpyModule);
// ...
McRFPy_API::default_texture = std::make_shared<PyTexture>("assets/kenney_tinydungeon.png", 16, 16);
PyModule_AddObject(m, "default_texture", McRFPy_API::default_texture->pyObject());
return m;
}
This module is being loaded into the embedded interpreter with PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
How to replicate the segfault
>>> type(mcrfpy.default_texture)
Segmentation fault (core dumped)
gdb points me to a problem with ob->ob_type
.
Oddly enough, there seems to be a workaround:
>>> mcrfpy.default_texture.__class__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'mcrfpy.Texture' object has no attribute '__class__'. Did you mean: '__hash__'?
>>> mcrfpy.default_texture.__class__
<class 'mcrfpy.Texture'>
>>> type(mcrfpy.default_texture)
<class 'mcrfpy.Texture'>
for some reason, __class__
is not defined at first, but after trying to access it, it is - and the type
function then works fine.
Py_SET_TYPE(obj, &PyTextureType);
does not seem to be of any help. Explicitly giving PyTextureType the default metaclass .tp_base = &PyBaseObject_Type
doesn’t seem to have any effect.
How can I instantiate these objects correctly? What other time bombs are lurking under the surface of these incompletely initialized objects I’m building?