简体   繁体   English

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

[英]Pass array of structs from Python to C

[Update: Problem solved! [更新:问题解决了! See bottom of the post] 见帖子的底部]

I need to allow python developers to pass an array of packed data (in this case vertices) into my API, which is a series of C++ interfaces exposed manually through the Python C API. 我需要允许python开发人员将打包数据数组(在本例中为顶点)传递给我的API,这是一系列通过Python C API手动公开的C ++接口。 My initial impression with this is to use the ctypes Structure class to allow for an interface like this: 我对此的初步印象是使用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

Where the function I'm trying to pass to has the following signature: 我试图传递的函数具有以下签名:

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

And the Python wrapper looks something like this: 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;
}

Of course, the biggest issue I have is this: I can retrieve the PyObject for the struct, but I have no idea how I would cast it to the correct type. 当然,我遇到的最大问题是:我可以检索结构的PyObject,但我不知道如何将它转换为正确的类型。 The above code fails miserably. 上面的代码悲惨地失败了。 So how exactly would I go about allowing the user to pass me this kind of data from Python? 那么我究竟会如何让用户从Python传递这种数据呢?

Now, a couple of things to consider: First is that I already have quite a bit of my Python/C++ layer written, and am perfectly happy with it (I moved away from SWIG so I could have more flexibility). 现在,需要考虑以下几点:首先,我已经编写了相当多的Python / C ++层,并且非常满意它(我离开了SWIG,因此我可以拥有更大的灵活性)。 I don't want to re-code it, so I would prefer a solution that works with the C API natively. 我不想重新编码它,所以我更喜欢本地使用C API的解决方案。 Second, I do intend to have the Vertex structure be pre-defined in my C++ code, so I would prefer to not have the user need to re-define it in the Python (cuts down on errors that way), but I'm not sure how to expose a contiguous structure like that. 其次,我打算在我的C ++代码中预先定义Vertex结构,所以我宁愿让用户不需要在Python中重新定义它(减少错误),但我是不知道如何暴露这样的连续结构。 Third, I have no reason for trying the ctypes structure aside from not knowing another way to do it. 第三,除了不知道另一种方法之外,我没有理由尝试ctypes结构。 Any suggestions are welcome. 欢迎任何建议。 Finally, since this is (as you may have guessed) for a graphics app I would prefer a faster method over a convenient one, even if the faster method takes a little bit more work. 最后,因为对于图形应用程序来说这是(正如您可能已经猜到的),我宁愿使用比方便的方法更快的方法,即使更快的方法需要更多的工作。

Thanks for any help! 谢谢你的帮助! I'm still feeling my way around python extensions, so it's a great help to get community input on some of the stickier parts. 我仍然对python扩展感兴趣,所以在一些更粘的部分上获得社区意见是一个很大的帮助。

[SOLUTION] [解]

So first off, thanks to everyone who pitched in their ideas. 首先,感谢所有投入他们想法的人。 It was a lot of little tidbits that added up to the eventual answer. 很多小花絮都是最终的答案。 In the end here is what I found: Sam's suggestion of using struct.pack ended up being right on the money. 最后这里是我发现的:Sam建议使用struct.pack最终是对的钱。 Seeing that I'm using Python 3, I had to tweak it ever so slightly, but when all was said and done this actually got a triangle showing up on my screen: 看到我正在使用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)

With my tuple parsing now looking like this: 我的元组解析现在看起来像这样:

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;
}

Note that even though I don't use the len variable in this example (though I will in the final product) I need to parse the tuple using 'y#' instead of just 'y' or else it will stop at the first NULL (according to the documentation). 请注意,即使我在这个例子中没有使用len变量(虽然我将在最终产品中)我需要使用'y#'而不是'y'来解析元组,否则它将在第一个NULL处停止(根据文件)。 Also to be considered: void* casts like this are quite dangerous, so please do loads more error checking than I show here! 还要考虑:void *这样的演员阵容非常危险,所以请加载更多错误检查,而不是我在这里展示!

So, job well done, happy day, pack up and go home, yes? 所以,干得好,快乐的一天,打包回家,是吗?

Wait! 等待! Not so fast! 没那么快! There's MORE! 还有更多!

Feeling good about how that all worked out I decided, on a whim, to see if my previous attempt still blew up on me and reverted back to the first snippet of python in this post. 对于这一切的成功感觉很好我一时兴起,决定看看我之前的尝试是否仍然炸毁了我并在这篇文章中又回到了第一段python片段。 (Using the new C code, of course) and... it worked! (当然,使用新的C代码)和...它的工作! The results were identical to the struct.pack version! 结果与struct.pack版本相同! Wow! 哇!

So this means your users have a choice in how they're going to provide this kind of data, and your code can handle either with no changes. 因此,这意味着您的用户可以选择如何提供此类数据,并且您的代码可以无需更改即可处理。 Personally I'm going to encourage the ctype.Structure method, since I think it makes for easier readability, but really it's whatever the user is comfortable with. 我个人会鼓励ctype.Structure方法,因为我觉得它更容易阅读,但实际上它是用户所熟悉的。 (Heck, they could manually type out a string of bytes in hex if they wanted to. It works. I tried.) (哎呀,如果他们愿意的话,他们可以用十六进制手动输入一串字节。它可以工作。我试过了。)

Honestly I think this is the best possible outcome, so I'm ecstatic. 老实说,我认为这是最好的结果,所以我欣喜若狂。 Thank you all again, and good luck to anyone else who runs into this problem! 再次感谢大家,祝所有遇到此问题的人好运!

Not tested but you should give this a try and let us know if its fast enough for your needs. 没有经过测试,但您应该尝试一下,如果它的速度足以满足您的需求,请告诉我们。

On the python side, pack the vertices into a string instead of an object. 在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

On the C library/python wrapper, modify your PyArgs_ParseTuple to use the format string "si" . 在C库/ python包装器上,修改PyArgs_ParseTuple以使用格式字符串"si" This will convert your python string into a C string (char*) which you can then typecast as a pointer to your vector struct. 这会将你的python字符串转换为C字符串(char *),然后可以将其作为指向vector struct的指针进行类型转换。 At this point the C string is a stream of bytes/words/floats and should be what you're looking for. 此时,C字符串是一个字节/单词/浮点数流,应该是您正在寻找的。

Good luck! 祝好运!

The easiest thing I can see to do would be to just avoid the issue altogether and expose a Device_ReadVertex that takes in x, y, z, u, v and color as arguments. 我能看到的最简单的事情就是完全避免这个问题并公开一个Device_ReadVertex,它接受x,y,z,u,v和color作为参数。 This has obvious drawbacks, like making the Python programmers feed it vertices one by one. 这有明显的缺点,比如让Python程序员逐个地为它提供顶点。

If that's not good enough (seems likely it isn't), then you could try defining a new Python type as described here . 如果这不够好(似乎不是),那么你可以尝试定义一个新的Python类型,如这里所述。 It's a bit more code but I think this is the "more architecturally sound" method, because you ensure your Python developers are using the same type definition as you are in the C code. 这是一些代码,但我认为这是“更具架构性的”方法,因为您确保您的Python开发人员使用与C代码相同的类型定义。 It also allows for a bit more flexibility than a simple struct (it's really a class, with the potential to add methods, etc), which I'm not sure you actually need but it might come in handy later. 它还允许比简单结构(它真的是一个类,有可能添加方法等)更多的灵活性,我不确定你真的需要它,但它可能会在以后派上用场。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM