Python`ctypes` - 如何将C函数返回的缓冲区复制到bytearray

时间:2017-06-04 11:25:36

标签: python ctypes

C函数(以下代码中的POINTER(c_ubyte)变量)返回指向image_data类型缓冲区的指针。我希望这些数据由Python管理,所以我想将其复制到bytearray。这是函数调用

image_data = stb_image.stbi_load(filename_cstr, byref(width),
                                 byref(height), byref(num_channels), 
                                 c_int(expected_num_channels))

我们只能在通话结束后了解图片的widthheight,因此无法预先分配bytearray

我会用

 array_type = c.c_ubyte * (num_channels.value * width.value * height.value)

 image_data_bytearray = bytearray(cast(image_data, array_type))

cast的类型必须是指针,而不是数组,所以我得到一个错误。

TypeError: cast() argument 2 must be a pointer type, not c_ubyte_Array_262144

我该怎么办?

2 个答案:

答案 0 :(得分:0)

好的,阅读评论中链接的问题的答案(谢谢,@" John Zwinck"和@" eryksun"),有两种存储数据的方式,在bytearraynumpy.array中。在所有这些摘录中,image_data的类型为POINTER(c_ubyte),我们将array_type定义为 -

array_type = c_ubyte * num_channels * width * height

我们可以先创建一个bytearray,然后循环并设置字节

arr_bytes = bytearray(array_size)
for i in range(array_size):
    arr_bytes[i] = image_data[i]

或者更好的方法是使用from_address创建C数组实例,然后用它初始化bytearray -

image_data_carray = array_type.from_address(addressof(image_data.contents))

# Copy into bytearray
image_data_bytearray = bytearray(image_data_carray)

在编写图像时(没有提出这个问题,只是为了完整性而共享),我们可以像这样获得指向bytearray数据的指针并将其提供给stbi_write_png

image_data_carray = array_type.from_buffer(image_data_bytearray)
image_data = cast(image_data_carray, POINTER(c_ubyte))

基于numpy的方式是在链接问题

中回答的
address = addressof(image_data.contents)
image_data_ptr = np.ctypeslib.as_array(array_type.from_address(address))

但是这仅仅指向C函数返回的内存,不会复制到Python管理的数组对象中。我们可以通过创建一个numpy数组来复制

image_data = np.array(image_data_ptr)

确认我在那里做了assert all(arr_np == arr_bytes)arr_np.dtypeuint8

在写入图像时,我们可以获得指向numpy数组的数据,如下所示

image_data = image_data_numpy.ctypes.data_as(POINTER(c_ubyte))

答案 1 :(得分:0)

您的变量array_type甚至不应该被调用,因为它实际上不是初始化的C数组,也不是任何类型的类型,而是为执行数组初始化而准备的Python对象。 那么,也不应该调用初始化数组。 :d

你应该做的相当于:

unsigned char array[channels*width*height];
C中的

然后,数组是指向N *类型的unsigned char的指针,指向数组的第一个字节。 (索引0) cast()应该获得一个指针来查看数据的类型。这样做:

array = (c.c_ubyte*(channels*width*height))()

应该做的伎俩。但是你不需要额外分配的内存。因此,您可以按照评论中的建议创建指针。

但我建议您使用:

image_data = bytearray(c.string_at(image_data))

它应该可以工作,当然,假设返回的图像为空终止。嗯,这也意味着使用签名的字符,但它不一定是。 如果您编写了C部分,只需在内存中分配一个额外的字节,该内存将包含一个声明/强制转换为包含无符号字符的图像,并将最后一项设置为0。 然后让算法像以前一样工作。如果你没有null终止它,你仍然会得到整个图像与string_at(),但会有3个字节或更多的内存泄漏。非常不受欢迎。

我在C模块中使用这个技巧进行颜色空间转换。它的工作速度非常快,因为没有循环,没有任何额外的东西。 string_at()只是拉入缓冲区并在其周围创建Python字符串包装器。 然后你可以使用numpy.fromstring(...)或array.array(“B”,image_data)或使用上面的bytearray()等。

否则,我刚才看到了你的回答。你也可以像你写的那样做,但我认为我的肮脏技巧更好(如果你可以更改C代码)。

P.S。哎呦!我刚刚在 doc 字符串中看到string_at()可以有一个可选的参数大小。也许使用它将完全忽略终止,并且不会有任何泄漏。我现在问自己为什么我没有在我的项目中使用它,但是因为空终止而搞砸了。 也许出于懒惰。使用大小不应该要求对C代码进行任何修改。所以它会是:

image_data = bytearray(c.string_at(image_data, channels*width*height))
相关问题