-
Notifications
You must be signed in to change notification settings - Fork 1
Plugin Implementation | Python
Generated code for Python plugins must provide in addition to the C headers glue code to:
- Functionality to Box and Unbox c types into python objects
- Functionality to Box the modules representing C Structs into python objects
Given a struct giving the C description of a module the following needs to be provided for a C API python implementation:
Consider the C Structs:
typedef struct{
int x;
int y;
}point2d;
//The python wrap type
typedef struct{
PyObject_HEAD
PyObject *x;
PyObject *y;
point2d *c_val;
}point2d_object;.
- PyObject_HEAD is a macro to set up python objects
- Each parameter in the C struct has a corresponding PyObject * parameter
- The PyObject has a non PyObject parameter; a pointer to the C struct that represents it
- PyObject's name is <cstructname></cstructname>_object
foo &eq Point2d() foo.x &eq 2
Stores the value 2 in foo->p->x instead of foo->x.
Three Functions must be defined for allocation, deallocation, and the method on python objects. The new function allocates memory on the heap, and nulls all pointers in the object's struct
static point2d_object* new_point2d(PyObject *args){ point2d_object *self; self = PyObject_new(point2d_object, &point2d_object_Type); if (self == NULL){ return NULL; } self->x = NULL; self->y = NULL; self->c_val = NULL; return self; }
Deallocation decrements the reference counters on the object's parameters, and then frees itself. The underlying C Struct is not touched here. As it isn't directly visible from python it must be manually managed (see later)
static void point2d_object_dealloc(point2d_object *self){ Py_XDECREF(self->x); Py_XDECREF(self->y); PyObject_del(self); }
Initialization performs the operations that a call to init in python would. As values are being converted from python to c types extra work occurs here:
static int point2d_object_init(point2d_object *self, PyObject *args, PyObject){ PyObject *x=NULL, *y=NULL, *tmp = NULL; //If Python allocated this object it will have no c struct attached to it. One will need to be allocated if self->c_val == NULL{ self->c_val = malloc(sizeof(Point2d)); } //PyArg_ParseTuple unpacks tuples using format strings http://docs.python.org/3/c-api/arg.html if (!PyArg_ParseTuple(args,"OO", x, y)){ return -1; } //Since content can find it's way into python objects in sneaky ways, we must //make sure any potential old data is garbage collected tmp = self->x; PyINCREF(x); self -> x = y; PyXDECREF(tmp) tmp = self->y; PyINCREF(y); self -> y = y; PyXDECREF(tmp) //TODO Unbox python objects into C Objects return 0; }
Since ownership of the underlying C struct isn't recorded a means to deallocate it seperately from the python object becomes necessary. A C function is created to deallocate the struct, and this function is placed in the PyObject's method table:
static void MADZ_Type_point2d_object_free(point2d_object *self, PyObject *args){ free(self->c_val); }
Functions on objects that are visible from python's reflection are stored in an array of PyMethodDef structs. These structs have four fields:
- char[] name, String name of method
- PyCFunction ml_meth :function pointer to c implementation
- int ml_flags: Flag for calling conventinon (usually METH_VARARGS)
- char [] doc: Method's PyDoc String.
static PyMethodDef point2d_methods[] ={
{"madz_free", (PyCFunction)MADZ_Type_point2d_object_free,METH_VARARGS, PyDoc_STR("You should Do Doc Forwarding")},
{NULL, NULL} //Bad method lookup sentinel
};
Members on objects that are visible from python's reflection are stored in array of PyMemberDef structs. Note here we leave the hidden c struct out of the table, to avoid direct python access to it.
- char * name: name of the member
- int type: the type of the member in the C struct
- Py_ssize_t: offset the offset in bytes that the member is located on the type’s object struct
- int flags: flag bits indicating if the field should be read-only or writable
- char * doc: points to the contents of the docstring
PyTypeObjects are a struct representing important type information about the implemented python type:
static PyTypeObject point2d_type= {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"module.Point2d", /*tp_name*/
sizeof(point2d_object), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)point2d_object_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"More Documentation Here", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
point2d_methods, /* tp_methods */
point2d_members, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Noddy_init, /* tp_init */
0, /* tp_alloc */
new_point2d, /* tp_new */
};
As we are wrapping accesses to c structs via python objects it becomes necessary to overwrite get_attr and set_attr to forward these access attempts:
static PyObject* point2d_object_getattr(point2d_object *self, char *name){
if (strcmp(name, "x") == 0){
PyObject *v = PyLong_FromLong(self->p->x);
if(v !=NULL){
Py_INCREF(v);
return v;
}
}
else if( strcmp(name, "y") == 0){
PyObject *v = PyLong_FromLong(self->p->y);
if(v !=NULL){
Py_INCREF(v);
return v;
}
}
else{
return Py_FindMethod(point2d_methods, (PyObject *)self, name);
}
static int point2d_object_setattr(point2d_object *self, char *name, PyObject *v){
if (strcmp(name, "x") == 0){
self->p->x = (int)PyNumber_AsSsize_t(v, NULL);
}
else if( strcmp(name, "y") == 0){
self->p->y = (int)PyNumber_AsSsize_t(v, NULL);
}
else{
return 0;
}
}
As incoming C times need to be boxed into PyObjects, and vis-versa conversion functions must be generated for each type.
In Addition to the generated code stubs for the plugin's defined functions. These functions must:
- Box incoming arguments into