从Python中分配SWIG C++类型的数组
我正在写一个Python脚本,这个程序通过SWIG把它的C++接口暴露出来。SWIG暴露的函数接口大概是这样的:
void writePixelsRect(JoxColor* colors, int left, int top, int width, int height);
JoxColor是一个简单的数据结构,长得像这样:
struct JoxColor {
float r, g, b, a;
};
我可以很简单地在Python中创建一个JoxColor,并调用writePixelsRect函数,像这样:
c = JoxApi.JoxColor()
c.r = r
c.g = g
c.b = b
c.a = a
JoxApi.writePixelsRect(c, x, y, 1, 1)
但是,如果我一次只调用writePixelsRect处理1x1像素的小矩形,那就太慢了。所以我想在Python中创建一个JoxColor的数组,这样我就可以一次性处理更大的矩形。请问用SWIG的类型能做到这一点吗?
需要注意的是,我没有办法访问暴露JoxColor和writePixelsRect的C++库的源代码,所以我不能为这个添加一个辅助函数。而且我也不想在系统中引入新的C++代码,因为那样会让使用我Python脚本的用户需要在他们的运行平台上编译C++代码。我可以在Python环境中使用ctypes,所以如果我能把用ctypes创建的浮点数组转换成JoxColor*的类型,那就可以解决我的问题。
2 个回答
除了特殊的类型映射,这个SWIG原型
void writePixelsRect(JoxColor* colors, int left, int top, int width, int height);
意味着colors
是一个类型为JoxColor
的单一对象,而不是一个数组。你只用一个对象的调用可以正常工作(虽然速度慢),这表明这个说法是对的。所以传递一个数组可能会导致SWIG包装代码出现类型不匹配的错误。
但老实说,这看起来像是一个可以绘制任意大小矩形的函数。所以如果你想画一个更大的矩形(用一种颜色),只需传入更大的宽度和/或高度:
JoxApi.writePixelsRect(c, x, y, 10, 20)
补充:
我之前没意识到你是在写SWIG包装器,我以为那是给你的。在这种情况下,你可以写一个类型映射,将Python列表(或元组,或者你想要的其他类型)转换为JoxColor*。SWIG文档中有一个示例,展示如何将Python字符串列表转换为char**:http://www.swig.org/Doc1.3/Python.html#Python_nn59。这个类型映射使用Python的C API来进行转换,你可以使用Python文档中提到的任何方法。基本上,你需要分配一个JoxColor数组,然后遍历Python列表对象,使用PyList_GetItem
来获取每个单独的对象。这样会返回一个SWIG包装的PyObject,你可以使用SWIG_ConvertPtr(list_item_py_object, (void**)&joxcolor_ptr, $descriptor(JoxColor *), 0)
将其转换为指向实际JoxColor元素的指针。然后你可以把它复制到你的数组中。
记住,JoxColor*
的类型映射会在所有出现JoxColor*
的地方生效,你可以说JoxColor* colors
来专门处理这个情况。
顺便说一下,默认情况下,SWIG将JoxColor*、JoxColor&、JoxColor和JoxColor[]以完全相同的方式包装,作为一个单一对象。Python只有对象,它不知道指针/引用/数组(Python列表也是对象)。http://www.swig.org/Doc1.3/Python.html#Python_nn22
这有点复杂,不过你能不能在这部分代码里,试试用纯粹的ctypes解决方案?基本上就是手动查看共享库文件里导出的符号,找到写入像素矩形的函数的名字。C++会对函数名进行处理,所以如果库的作者选择了用extern "C"
,那么函数名可能就是writePixelsRect
,但如果没有,那可能会变得很复杂,比如_Z15writePixelsRectP8JoxColoriiii
(这是我在系统上创建的一个虚拟C++库里导出的名字)。
在Linux上,你可以用这个命令来查看符号名:
nm libjox.so | grep writePixel | cut -d " " -f 3
然后,把那个字符串保存下来,插入到Python代码里,像这样:
from ctypes import *
LIBRARY_NAME = 'libjox.so'
c = cdll.LoadLibrary(LIBRARY_NAME)
WIDTH = 20
HEIGHT = 20
class JoxColor(Structure):
_fields_ = [("r", c_float), ("g", c_float), ("b", c_float), ("a", c_float)]
ColorBlock = JoxColor * (WIDTH * HEIGHT)
data_array = ColorBlock()
color = JoxColor(0, 0, 1, 0)
for i in range(WIDTH * HEIGHT):
data_array[i] = color
c._Z15writePixelsRectP8JoxColoriiii(data_array, 0, 0, WIDTH, HEIGHT)
假设_Z15writePixelsRectP8JoxColoriiii
是共享库中可以访问的函数名。运行这段代码在我系统上的一个虚拟库里是可以成功的:
#include <stdio.h>
struct JoxColor {
float r, g, b, a;
};
void writePixelsRect(JoxColor *colors, int left, int top, int width, int height) {
int p = 0;
printf("size: %i, %i\n", width, height);
for (int i = 0; i < width; i++) {
for (int j = 0; j < height; j++) {
JoxColor color = colors[p];
printf("pixel: %f, %f, %f, %f\n", color.r, color.g, color.b, color.a);
}
}
}
所以我希望在你的环境里,这段代码也能顺利运行。