代码之家  ›  专栏  ›  技术社区  ›  Toji

将结构数组从python传递到c

  •  9
  • Toji  · 技术社区  · 14 年前

    [更新:问题解决!见文章底部]

    我需要允许Python开发人员将一组打包数据(在这种情况下是顶点)传递到我的API中,这是通过Python C API手动暴露的一系列C++接口。我最初的印象是使用ctypes结构类来允许这样的接口:

    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++代码中被预定义,所以我不希望用户需要在Python中重新定义它(这样减少错误),但是我不知道如何暴露这样一个连续的结构。第三,除了不知道另一种方法之外,我没有理由尝试CTypes结构。欢迎提出任何建议。最后,由于这是一个图形应用程序(正如您可能已经猜到的那样),我更喜欢一个更快的方法,而不是一个方便的方法,即使更快的方法需要更多的工作。

    谢谢你的帮助!我仍然对python扩展很感兴趣,所以这对获得一些更为棘手的部分的社区输入非常有帮助。

    [解决方案]

    首先,感谢所有提出自己想法的人。最终的答案是由许多小道消息加起来的。最后,我发现:萨姆关于使用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”来解析元组,否则它将在第一个空值处停止(根据文档)。还要考虑:像这样的void*类型转换非常危险,所以请加载比我这里显示的更多的错误检查!

    那么,干得好,快乐的一天,收拾行李回家,是吗?

    等待!不要这么快!还有更多!

    我对这一切的结果感觉良好,一时兴起,决定看看我之前的尝试是否仍然对我产生了影响,然后又回到了本文中的第一段python代码。(当然是使用新的C代码)和…它奏效了!结果与struct.pack版本相同!真的!

    这意味着你的用户可以选择他们将如何提供这种数据,而你的代码可以在不做任何更改的情况下进行处理。就我个人而言,我将鼓励使用ctype.structure方法,因为我认为它有助于提高可读性,但实际上它是用户所能接受的。(如果他们愿意的话,他们可以手动输入十六进制的字节串。它起作用了。我试过了。

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

    2 回复  |  直到 14 年前
        1
  •  2
  •   Sam Post    14 年前

    没有测试,但你应该尝试一下,让我们知道它是否足够快,以满足你的需要。

    在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*),然后您可以将其作为指向向量结构的指针进行类型转换。此时,C字符串是一个字节/字/浮点数流,应该是您要查找的内容。

    祝你好运!

        2
  •  1
  •   xitrium    14 年前

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

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