使用Ctypes从Python传递C结构指针

时间:2014-01-09 14:03:40

标签: python c pointers structure ctypes

我有一个 yec.c 文件,用于定义具有两个函数的结构:

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

我在以下 setup_yec.py 文件中使用python setup_yec.py build命令将yec.c文件编译成yec.so:

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])

我可以在Python下使用我的编译库,而nopoint()函数可以工作:

import yec
yec.nopoint(3, 4)

我想使用第二个功能;我的库的viapoint()应该接受来自Python的结构指针,我在其中定义相关的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))

当然,它不起作用:

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

如果有人知道如何修改viapoint()函数以便能够通过PyArg_ParseTuple()解析结构指针以及如何在python中传递python结构指针(使用byref?),那将是一个很好的帮助。

感谢。

2 个答案:

答案 0 :(得分:3)

您可以将Structure解析为读写缓冲区("w#")。通过将其作为参数传递,您可以放心,它是一个引用的对象。它还确保传入的缓冲区是正确大小的可写内存。崩溃的Python是不可接受的。你应该在Python中获得异常。如果你有Python代码使解释器的段错误变得微不足道,那你就错了。

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)

如果您只接受一个地址作为参数,您的函数可能会对无效指针进行段错误,或者只是返回垃圾或修改内存,这将使您稍后以一种难以理解的方式崩溃。此外,通过解析地址,您需要有条件地定义是否在预处理器中解析longlong long,具体取决于void *long相比的大小。例如,在Win64上,long为32位,解析指针为long会截断它。最后,要求您首先在Python中调用addressof的API是一种效率低下的方法。

答案 1 :(得分:2)

您需要使用Python脚本中的ctypes.addressof,而不是ctypes.byref(与C指针不同的对象),然后在yec.c中解析输入值作为long(或int,如果在32位)并将其分配给“struct mec *”。

见下面一个工作示例:

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

并在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))

运行它,我得到:

> python run.py                                                                                   
py mec class 1 2
viapoint av(c) age nb: 1 et 2!
viapoint ap(c) age nb: 10 et 1!