【问题标题】:Pass a C Structure pointer from Python using Ctypes使用 Ctypes 从 Python 传递一个 C 结构指针
【发布时间】:2014-01-28 03:09:42
【问题描述】:

我有一个 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?),那将是一个很大的帮助。

谢谢。

【问题讨论】:

    标签: python c pointers structure ctypes


    【解决方案1】:

    您可以将 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)
    

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

    【讨论】:

    • 好的,使用读写缓冲方式更安全!我将更改我的代码。谢谢。
    • 如果我在 C 中创建结构,并通过 c 库中的函数返回其指针。我是否只是将它存储在 python 中的变量中,还是在 python 中做另一个技巧以在 c 中使用它,如你所描述的那样?
    • 我的答案中的方法仅适用于使用可写缓冲区实现缓冲区接口的 Python 对象。您可以将它与 ctypes 数据类型或 NumPy 数组等一起使用。通过 Python 偷运指针的最佳方法是将其包装在 PyCapsule 中。说你malloc 结构的内存。然后使用在指针上调用free 的析构函数创建胶囊。
    • 好的,我会试试 py_capusles。非常感谢。
    • 我刚刚看到 numpy 允许我们以 c 类型发送数据浮点数组:data.ctypes.data_as(ctypes.POINTER(ctypes.c_float))。它的工作方式是否与在 PyArg_ParseTuple 中具有读写缓冲区的 struct mec 相同?
    【解决方案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!
    

    【讨论】:

    • 完美,有效。与读写缓冲区(“w#”)相比,我更容易使用此解决方案。我想知道以某种方式使用指针或读写缓冲区选项是否会有所不同?一次偶然的机会,我发现在 python 中使用 c 结构的另一种方法是使用 PyCapsule。对于对此感兴趣的人,这是我找到的link。
    • 或者您可以使用 Cython,我认为这是一款了不起的软件。
    • 我从一位同事那里得到了一个准备好使用的 c 代码,我只是想在 python 中处理费力的数据预处理。但我应该花时间看看 cython!谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-04
    • 2017-01-22
    • 2013-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多