将结构数组从Python传递给C.

时间:2010-02-21 20:07:51

标签: python python-3.x structure python-c-api

[更新:问题解决了!见帖子的底部]

我需要允许python开发人员将打包数据(在本例中为顶点)数组传递到我的API中,这是一系列通过Python C API手动公开的C ++接口。我对此的初步印象是使用ctypes Structure类来允许这样的接口:

class Vertex(Structure):
_fields_ = [
    ('x', c_float),
    ('y', c_float),
    ('z', c_float),
    ('u', c_float),
    ('v', c_float),
    ('color', c_int)
] 

verts = (Vertex * 3)()
verts[0] = Vertex(0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF)
verts[1] = Vertex(0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF)
verts[2] = Vertex(-0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF)

device.ReadVertices(verts, 3) # This is the interfaces to the C++ object

我试图传递的函数具有以下签名:

void Device::ReadVertices(Vertex* verts, int count);

Python包装器看起来像这样:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args)
{
    PyObject* py_verts;
    int count;

    if(!PyArg_ParseTuple(args, "Oi", &py_verts, &count)) 
        return NULL;

    // This Doesn't Work!
    Vertex* verts = static_cast<Vertex*>(PyCObject_AsVoidPtr(py_verts));

    self->device->ReadVertices(verts, count);

    Py_RETURN_NONE;
}

当然,我遇到的最大问题是:我可以检索结构的PyObject,但我不知道如何将它转换为正确的类型。上面的代码悲惨地失败了。那么我将如何允许用户从Python传递这种数据?

现在,需要考虑以下几点:首先,我已经编写了很多我的Python / C ++层,并且非常满意它(我离开了SWIG,因此我可以拥有更多的灵活性)。我不想重新编码它,所以我更喜欢本地使用C API的解决方案。其次,我打算在我的C ++代码中预先定义Vertex结构,所以我宁愿让用户不需要在Python中重新定义它(减少错误),但我是不知道如何暴露这样的连续结构。第三,除了不知道另一种方法之外,我没有理由尝试ctypes结构。欢迎任何建议。最后,由于这是(对于你可能已经猜到的)图形应用程序,我更喜欢比方便的方法更快的方法,即使更快的方法需要更多的工作。

感谢您的帮助!我仍然对python扩展感兴趣,所以在一些更粘的部分上获得社区意见是非常有帮助的。

[溶液]

首先,感谢所有投入他们想法的人。很多小花絮都是最终的答案。最后这里是我发现的:Sam建议使用struct.pack最终是对的钱。看到我使用的是Python 3,我不得不稍微调整一下,但是当说完所有这一切后,实际上我的屏幕上出现了一个三角形:

verts = bytes()
verts += struct.pack("fffffI", 0.0, 0.5, 0.0, 0.0, 0.5, 0xFF0000FF)
verts += struct.pack("fffffI", 0.5, -0.5, 0.0, 0.5, -0.5, 0x00FF00FF)
verts += struct.pack("fffffI", -0.5, -0.5, 0.0, -0.5, -0.5, 0x0000FFFF)

device.ReadVertices(verts, 3)

我的元组解析现在看起来像这样:

static PyObject* Device_ReadVertices(Py_Device* self, PyObject* args)
{
    void* py_verts;
    int len, count;

    if(!PyArg_ParseTuple(args, "y#i", &py_verts, &len, &count)) 
        return NULL;

    // Works now!
    Vertex* verts = static_cast<Vertex*>(py_verts);

    self->device->ReadVertices(verts, count);

    Py_RETURN_NONE;
}

请注意,即使我在这个例子中没有使用len变量(尽管我会在最终产品中),我需要使用'y#'而不是'y'来解析元组,否则它会在第一个NULL处停止(根据文档)。另外要考虑:void *这样的演员是非常危险的,所以请加载更多错误检查,而不是我在这里显示!

所以,干得好,快乐的一天,打包回家,是吗?

等待!没那么快!还有更多!

对于这一切的成功感觉很好我一时兴起决定看看我之前的尝试是否仍然炸毁了我并在这篇文章中又回到了第一段python片段。 (当然,使用新的C代码)和...它的工作!结果与struct.pack版本相同!哇!

因此,这意味着您的用户可以选择如何提供此类数据,并且您的代码可以无需更改即可处理。我个人会鼓励ctype.Structure方法,因为我觉得它更容易阅读,但实际上它是用户所熟悉的。 (哎呀,如果他们愿意的话,他们可以用十六进制手动输入一串字节。它可以工作。我试过了。)

老实说,我认为这是最好的结果,所以我欣喜若狂。再次感谢大家,祝所有遇到此问题的人好运!

2 个答案:

答案 0 :(得分:2)

未经测试,但您应该尝试一下,如果它足够快,请告诉我们。

在python端,将顶点打包成字符串而不是对象。

str = "" # byte stream for encoding data
str += struct.pack("5f i", vert1.x, vert1.y, vert1.z, vert1.u, vert1.v, vert1.color) # 5 floats and an int
# same for other vertices

device. ReadVertices( verts, 3) # send vertices to C library

在C库/ python包装器上,修改PyArgs_ParseTuple以使用格式字符串"si"。这会将你的python字符串转换为C字符串(char *),然后可以将其作为指向vector struct的指针进行类型转换。此时,C字符串是一个字节/单词/浮点数流,应该是您正在寻找的。 祝你好运!

答案 1 :(得分:1)

我能看到的最简单的事情就是完全避免这个问题并暴露一个以x,y,z,u,v和color作为参数的Device_ReadVertex。这有一些明显的缺点,比如让Python程序员一个接一个地为它提供顶点。

如果这不够好(似乎不是),那么你可以尝试定义一个新的Python类型,如here所述。这是一些代码,但我认为这是“更具架构性的”方法,因为您确保您的Python开发人员使用与C代码相同的类型定义。它还允许比简单结构更灵活(它实际上是一个类,有可能添加方法等),我不确定你真的需要它,但它可能会在以后派上用场。