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

使用Ctypes从Python传递C结构指针,python,c,pointers,structure,ctypes,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", &

我有一个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);
}
我可以在Python下使用编译后的库,nopoint()函数可以工作:

import yec
yec.nopoint(3, 4)
我想使用第二个函数;我的库的viapoint(),它应该接受来自Python的结构指针,我在Python中定义了相关的ctypes。结构:

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
回溯(最近一次呼叫最后一次):
文件“testyec.py”,第18行,在
yec.viapoint(byref(m))
TypeError:必须是不可能的,而不是CargoObject
如果有人知道如何修改viapoint()函数,以便能够通过PyArg_ParseTuple()解析结构指针,以及如何在python中传递python结构指针(使用byref?),那将是一个很大的帮助


谢谢。

您需要使用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 run.py                                                                                   
py mec class 1 2
viapoint av(c) age nb: 1 et 2!
viapoint ap(c) age nb: 10 et 1!

您可以将
结构
解析为读写缓冲区(
“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)


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

Perfect,它可以工作。与读写缓冲区(“w#”)相比,我更容易使用此解决方案。我想知道使用指针作为long或读写缓冲区选项是否会有所不同?碰巧,我发现在python中使用c结构的另一种方法是使用PyCapsule。对于对此感兴趣的人,这里有一个我发现的。或者你可以使用Cython,在我看来,这是一个了不起的软件。我从一位同事那里得到了一个现成的c代码,我只想用python处理费劲的数据预处理。但是我应该花点时间看看cython!谢谢。好的,使用读写缓冲区方法更安全!我将更改我的代码。谢谢。如果我用C语言创建了这个结构,并通过C库中的函数返回它的指针。我是简单地将其存储在python中的变量中,还是像您所描述的那样,在python中有另一个技巧可以在c中使用它?我的答案中的方法仅适用于使用可写缓冲区实现缓冲区接口的python对象。您可以将它与ctypes数据类型或NumPy数组等一起使用。通过Python走私指针的最佳方法是将其包装在
PyCapsule
中。假设您
malloc
struct的内存。然后用一个析构函数创建胶囊,该析构函数在指针上调用
free
。好的,我将尝试py_capusles。非常感谢。我刚刚看到numpy允许我们以c类型发送数据浮点数组:
data.ctypes.data\u as(ctypes.POINTER(ctypes.c\u float))
。它的工作方式是否与PyArg_ParseTuple中具有读写缓冲区的struct mec相同?
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);
}
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)