Pass a C Structure pointer from Python using Ctypes

c, ctypes, pointers, python, structure

Solution

You can parse the `Structure` as a read-write buffer (`"w#"`). By passing it as an argument you can rest assured that it's a referenced object. It also ensures that the passed in buffer is writable memory of the correct size. Crashing Python is not acceptable. You're supposed to get exceptions in Python. If you have Python code that makes it trivial to segfault the interpreter, you're doing it very wrong.

static PyObject* viapoint(PyObject* self, PyObject* args)
{
    struct mec *m;
    size_t size;

    if (!PyArg_ParseTuple(args, "w#", &m, &size))
        return NULL;

    if (size != sizeof(struct mec)) {
        PyErr_SetString(PyExc_TypeError, "wrong buffer size");
        return NULL;
    }

    printf("viapoint av(c) age nb: %d et %d!\n", m->age, m->number);
    m->age = 10;
    m->number = 1;

    return Py_BuildValue("i", m->age + m->number);
}

Python:

from ctypes import *
import yec

class Mec(Structure):
    _fields_ = [
        ("age", c_int),
        ("number", c_int),
    ]

class Bad(Structure):
    _fields_ = [
        ("age", c_int),
        ("number", c_int),
        ("extra", c_int),
    ]

m = Mec(1, 2)
print yec.viapoint(m)

# TypeError
b = Bad(1, 2, 3)
print yec.viapoint(b)

If you just accept an address as the argument, your function might segfault on an invalid pointer, or just return garbage or modify memory that will make you program crash later in an inscrutable way. Moreover, by parsing the address you'll need to conditionally define whether to parse as `long` or `long long` in the preprocessor, depending on the size of `void *` compared to `long`. For example, on Win64 a `long` is 32bit and parsing a pointer as `long` truncates it. Finally, an API that requires you to first call `addressof` in Python is an inefficient kludge.

Problem

I have a yec.c file defining a structure with two functions: ``` #include <python2.7/Python.h> struct mec { int age; int number; }; static PyObject* nopoint(PyObject* self, PyObject* args) { struct mec m; int n1, n2; if (!PyArg_ParseTuple(args, "ii", &n1, &n2)) return NULL; printf("nopoint(c) nombres: %d et %d!\n", n1, n2); m.age = n1; m.number = n2; printf("nopoint(c) age nb: %d et %d!\n", m.age, m.number); return Py_BuildValue("i", n1 + n2); } static PyObject* viapoint(PyObject* self, PyObject* args) { struct mec *m; if (!PyArg_ParseTuple(args, "o", &m)) return NULL; printf("viapoint av(c) age nb: %d et %d!\n", m->age, m->number); m->age = 10; m->number = 1; printf("viapoint ap(c) age nb: %d et %d!\n", m->age, m->number); return Py_BuildValue("i", m->age + m->number); } static PyMethodDef MyYecMethods[] = { {"nopoint", nopoint, METH_VARARGS, "Description de fune"}, {"viapoint", viapoint, METH_VARARGS, "Description de fdeux"}, {NULL, NULL, 0, NULL} }; PyMODINIT_FUNC inityec(void) { (void) Py_InitModule("yec", MyYecMethods); } ``` I compiled the yec.c file into yec.so with a `python setup_yec.py build` command on the following setup_yec.py file: ``` from distutils.core import setup, Extension module1 = Extension('yec', sources = ['yec.c']) setup (name = 'YecPkg', version = '1.0', description = 'This is a demo of yec pkg', ext_modules = [module1]) ``` I can use my compiled library under Python and the nopoint() function works: ``` import yec yec.nopoint(3, 4) ``` I would like to use the second function ; viapoint() of my library which should accepts a struct pointer from Python where I define the related ctypes.Structure: ``` from ctypes import * class Mec(Structure): _fields_ = [("age", c_int), ("number", c_int)] m = Mec(1, 2) print "py mec class", m.age, m.number yec.viapoint(byref(m)) ``` Of course, it does not work: ``` Traceback (most recent call last): File "testyec.py", line 18, in <module> yec.viapoint(byref(m)) TypeError: must be impossible<bad format char>, not CArgObject ``` If someone knows how to modify the viapoint() function to be able to parse the structure pointer via the PyArg_ParseTuple() and how to pass the python structure pointer in python (using byref?), it would be a great help. Thanks.

Original source