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

Toj*_*oji 9 python structure python-c-api python-3.x

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

我需要允许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
Run Code Online (Sandbox Code Playgroud)

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

void Device::ReadVertices(Vertex* verts, int count);
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

当然,我遇到的最大问题是:我可以检索结构的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)
Run Code Online (Sandbox Code Playgroud)

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

Sam*_*ost 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
Run Code Online (Sandbox Code Playgroud)

在 C 库/python 包装器上,修改 PyArgs_ParseTuple 以使用格式 string "si"。这会将您的 python 字符串转换为 C 字符串 (char*),然后您可以将其类型转换为指向向量结构的指针。此时,C 字符串是字节/字/浮点流,应该就是您要寻找的内容。

祝你好运!