Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass a C Structure pointer from Python using Ctypes

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.

like image 474
user1520280 Avatar asked Nov 29 '25 10:11

user1520280


2 Answers

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.

like image 74
Eryk Sun Avatar answered Nov 30 '25 22:11

Eryk Sun


You need to use ctypes.addressof from the Python script, rather than ctypes.byref (which is a different object from a C pointer), and then, in yec.c, parse the input value as a long (or int if in 32bits) and assign it to the "struct mec *".

See below a working example:

#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, "l", &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);
}

and in Python:

from ctypes import *
import yec


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


m = Mec(1, 2)

print "py mec class", m.age, m.number

yec.viapoint(addressof(m))

Running it, I get:

> python run.py                                                                                   
py mec class 1 2
viapoint av(c) age nb: 1 et 2!
viapoint ap(c) age nb: 10 et 1!
like image 44
lbolla Avatar answered Nov 30 '25 22:11

lbolla



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!