在不同项大小下使用Py_buffer和PyMemoryView_FromBuffer

6 投票
1 回答
5977 浏览
提问于 2025-04-17 18:06

这个问题和我之前问的一个问题有关。具体来说,就是这个,如果有人感兴趣的话。简单来说,我想把一个C语言的数组暴露给Python,使用的是一个用memoryview包装的Py_buffer。我已经通过PyBuffer_FillInfo让它工作了(也就是说,我可以在Python中操作数据,并在C中将其写入标准输出),但是如果我尝试自己创建一个缓冲区,C函数返回后就会出现段错误。

我需要创建自己的缓冲区,因为PyBuffer_FillInfo假设格式是字符,这样项大小字段就变成了1。我需要能够提供大小为1、2、4和8的项。

这里有一些代码,这是一个有效的示例:

Py_buffer *buf = (Py_buffer *) malloc(sizeof(*buf));
int r = PyBuffer_FillInfo(buf, NULL, malloc(sizeof(char) * 4), 4, 0, PyBUF_CONTIG);
PyObject *mv = PyMemoryView_FromBuffer(buf);
//Pack the memoryview object into an argument list and call the Python function
for (blah)
  printf("%c\n", *buf->buf++); //this prints the values i set in the Python function

查看PyBuffer_FillInfo的实现,它其实很简单,所以我自己写了一个函数来提供自定义的项大小:

//buffer creation function
Py_buffer *getReadWriteBuffer(int nitems, int itemsize, char *fmt) {
  Py_buffer *buf = (Py_buffer *) malloc(sizeof(*buf));
  buf->obj = NULL
  buf->buf = malloc(nitems * itemsize);
  buf->len = nitems * itemsize;
  buf->readonly = 0;
  buf->itemsize = itemsize;
  buf->format = fmt;
  buf->ndim = 1;
  buf->shape = NULL;
  buf->strides = NULL;
  buf->suboffsets = NULL;
  buf->internal = NULL;
  return buf;
}

我怎么使用它:

Py_buffer *buf = getReadWriteBuffer(32, 2, "h");
PyObject *mv = PyMemoryView_FromBuffer(buf);
// pack the memoryview into an argument list and call the Python function as before

for (blah)
  printf("%d\n", *buf->buf); //this prints all zeroes even though i modify the array in Python

return 0;
//the segfault happens somewhere after here

使用我自己的缓冲区对象的结果是在C函数返回后出现段错误。我真的不明白为什么会发生这种情况。任何帮助都会非常感激。

编辑 根据这个问题,我之前没找到,项大小大于1可能根本不被支持。这让这个问题变得更加有趣。也许我可以使用PyBuffer_FillInfo,用一个足够大的内存块来存放我想要的内容(例如32个C语言的浮点数)。在这种情况下,问题更多的是关于如何在Python函数中将Python的浮点数赋值给memoryview对象。问题真是层出不穷。

1 个回答

5

由于没有找到答案,我决定换个方法来解决问题,而不是我最开始想的那样。我把这个留在这里,以防其他人也遇到同样的问题。

简单来说,我没有在C语言中创建一个缓冲区(或者说字节数组),然后把它传给Python让用户去修改。相反,我稍微调整了一下代码,让用户从Python的回调函数中返回一个字节数组(或者任何支持缓冲区接口的类型)。这样一来,我就不需要担心返回对象的大小,因为在我的情况下,所有C代码要做的就是提取这个对象的缓冲区,然后用一个简单的memcpy把它复制到另一个缓冲区。

代码:

PYGILSTATE_ACQUIRE; //a macro i made
PyObject *result = PyEval_CallObject(python_callback, NULL);
if (!PyObject_CheckBuffer(result))
  ; //raise exception

Py_buffer *view = (Py_buffer *) malloc(sizeof(*view));
int error = PyObject_GetBuffer(result, view, PyBUF_SIMPLE);
if (error)
  ; //raise exception

memcpy(my_other_buffer, view->buf, view->len);

PyBuffer_Release(view);
Py_DECREF(result);
PYGILSTATE_RELEASE; //another macro

希望这能帮助到某个人。

撰写回答