将结构数组从Python传递到C [英] Pass array of structs from Python to C

查看:109
本文介绍了将结构数组从Python传递到C的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

[更新:问题已解决!请参阅文章底部]

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

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. My initial impression with this is to use the ctypes Structure class to allow for an interface like this:

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

Python包装器看起来像这样:

And the Python wrapper looks something like this:

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向我传递这种数据呢?

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. 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/C ++层,并且对此感到非常满意(我离开了SWIG,因此可以拥有更大的灵活性).我不想对其进行重新编码,因此我希望使用一种与C API本机一起使用的解决方案.其次,我确实打算在我的C ++代码中预定义Vertex结构,所以我希望用户不需要在Python中重新定义它(这样可以减少错误),但是我不知道如何公开这样的连续结构.第三,除了不知道另一种方法之外,我没有理由尝试ctypes结构.欢迎任何建议.最后,由于(正如您可能已经猜到的那样)对于图形应用程序,我宁愿选择一种更快的方法,而不是一种便捷的方法,即使该更快的方法需要更多的工作.

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). I don't want to re-code it, so I would prefer a solution that works with the C API natively. 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. Third, I have no reason for trying the ctypes structure aside from not knowing another way to do it. 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.

感谢您的帮助!我仍然对python扩展感到不满意,因此,在一些棘手的部分上获得社区的投入是很大的帮助.

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.

[解决方案]

首先,感谢所有提出自己想法的人.最终的答案是很多小窍门.最后,我发现了这一点:Sam提出的使用struct.pack的建议最终是对的.看到我使用的是Python 3,我不得不对其进行了一些微调,但是当全部说完之后,实际上在屏幕上显示了一个三角形:

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. 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:

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

请注意,即使在此示例中我不使用len变量(尽管我将在最终产品中使用),我仍需要使用'y#'而不是'y'来解析元组,否则它将在第一个NULL处停止(根据文档).还应考虑:void *这样的强制转换非常危险,因此请进行比我在此处显示的更多的错误检查!

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). Also to be considered: void* casts like this are quite dangerous, so please do loads more error checking than I show here!

那么,工作做得好,快乐的一天,收拾行装回家,是吗?

So, job well done, happy day, pack up and go home, yes?

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

Wait! Not so fast! There's MORE!

我对所有这些工作的结果感到很满意,于是一时兴起,决定看看我以前的尝试是否仍然对我产生冲击,并恢复到本文的第一个python代码段. (当然,使用新的C代码)并且...奏效了!结果与struct.pack版本相同!哇!

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. (Using the new C code, of course) and... it worked! The results were identical to the struct.pack version! Wow!

因此,这意味着您的用户可以选择如何提供此类数据,并且您的代码可以在不做任何更改的情况下进行处理.我个人将鼓励使用ctype.Structure方法,因为我认为它可以简化可读性,但实际上,这是用户感到满意的. (哎呀,如果愿意的话,他们可以手动输入一个十六进制的字节串.它起作用.我尝试过.)

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. (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.

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

On the python side, pack the vertices into a string instead of an object.

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字符串是字节/字/浮点数的流,应该是您要查找的内容.

On the C library/python wrapper, modify your PyArgs_ParseTuple to use the format string "si". This will convert your python string into a C string (char*) which you can then typecast as a pointer to your vector struct. At this point the C string is a stream of bytes/words/floats and should be what you're looking for.

祝你好运!

这篇关于将结构数组从Python传递到C的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆