Skip to content

Plugin Implementation | Python

Mason Bially edited this page Sep 26, 2013 · 2 revisions

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

Table of Contents

Python Modules in C API

Given a struct giving the C description of a module the following needs to be provided for a C API python implementation:

PyObject Struct

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
The actual data associated with the PyObject is kept in its underlying c Struct. Code is generated to forward read and writes of these variables (in python) to the underlying C struct. Thus:
 foo &amp;eq Point2d()
  foo.x &amp;eq 2

Stores the value 2 in foo->p->x instead of foo->x.

Allocation, Deallocation, and Initialization

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&#42; new_point2d(PyObject &#42;args)&#123;
 point2d_object &#42;self&#59;
 self &#61; PyObject_new(point2d_object, &amp;point2d_object_Type)&#59;
 if (self &#61;&#61; NULL)&#123;
  return NULL&#59;
 &#125;
 self&#45;&gt;x &#61; NULL&#59;
 self&#45;&gt;y &#61; NULL&#59;
 self&#45;&gt;c_val &#61; NULL&#59;
 return self&#59;
 &#125;

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 &#42;self)&#123;
  Py_XDECREF(self&#45;&gt;x)&#59;
  Py_XDECREF(self&#45;&gt;y)&#59;
  PyObject_del(self)&#59;
 &#125;

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 &#42;self, PyObject &#42;args, PyObject)&#123;
 PyObject &#42;x&#61;NULL, &#42;y&#61;NULL, &#42;tmp &#61; NULL&#59;
 //If Python allocated this object it will have no c struct attached to it. One will need to be allocated
 if self&#45;&gt;c_val &#61;&#61; NULL&#123;
  self&#45;&gt;c_val &#61; malloc(sizeof(Point2d))&#59;
 &#125;
 //PyArg_ParseTuple unpacks tuples using format strings http&#58;//docs.python.org/3/c&#45;api/arg.html
 if (&#33;PyArg_ParseTuple(args,&quot;OO&quot;, x, y))&#123;
  return &#45;1&#59;
 &#125;
 //Since content can find it&#39;s way into python objects in sneaky ways, we must
 //make sure any potential old data is garbage collected
 tmp &#61; self&#45;&gt;x&#59;
 PyINCREF(x)&#59;
 self &#45;&gt; x &#61; y&#59;
 PyXDECREF(tmp)
 tmp &#61; self&#45;&gt;y&#59;
 PyINCREF(y)&#59;
 self &#45;&gt; y &#61; y&#59;
 PyXDECREF(tmp)
 //TODO Unbox python objects into C Objects
 return 0&#59;
 &#125;

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 &#42;self, PyObject &#42;args)&#123;
  free(self&#45;&gt;c_val)&#59;
 &#125;

Method Table

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.
These are usuallly defined inline as arrays:
 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
  };

Member Table

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
These are usually defined inline as arrays:



PyTypeObject

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 */
 };

GetAttr and SetAttr

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;
  }
 }

Type Conversions

As incoming C times need to be boxed into PyObjects, and vis-versa conversion functions must be generated for each type.

Plugin Functions

In Addition to the generated code stubs for the plugin's defined functions. These functions must:

  • Box incoming arguments into

MADZ_Plugin Types

Clone this wiki locally