如何在 Cython 中的新样式缓冲区对象中包装 C 指针和长度



我正在用Cython编写Python 2.7扩展模块。 如何创建一个 Python 对象,实现新式缓冲区接口,该接口包装 C 库提供给我的内存块? 内存块只是一个字节串,而不是结构或多维数组。 我得到了一个const void *指针和一个长度,以及有关指针保持有效期的一些详细信息。

我无法复制内存 — 这会降低应用程序的性能。

对于旧式缓冲区对象,我可以简单地使用 PyBuffer_FromMemory() ,但我似乎找不到类似的简单方法来生成新式缓冲区对象。

我是否必须创建自己的类来实现缓冲区接口? 还是Cython提供了一种简单的方法来做到这一点?

我已经从 Cython 文档中阅读了 Unicode 和传递字符串以及类型化内存视图页面,但文档不精确且不是很完整,并且没有看起来与我想做的类似的示例。

这是我尝试过的(test.pyx):

from libc.stdlib cimport malloc
from libc.string cimport memcpy
## pretend that this function is in some C library and that it does
## something interesting.  (this function is unrelated to the problem
## I'm experiencing -- this is just an example function that returns a
## chunk of memory that I want to wrap in an object that follows the
## new buffer protocol.)
cdef void dummy_function(const void **p, size_t *l):
    cdef void *tmp = malloc(17)
    memcpy(tmp, "some test bytes", 17)
    p[0] = tmp
    l[0] = 17
cpdef getbuf():
    cdef const void *cstr
    cdef size_t l
    dummy_function(&cstr, &l)
    ## error: test.pyx:21:20: Invalid base type for memoryview slice: void
    #cdef const void[:] ret = cstr[:l]
    ## error: test.pyx:24:9: Assignment to const 'ret'
    #cdef const char[:] ret = cstr[:l]
    ## error: test.pyx:27:27: Cannot convert 'void const *' to memoryviewslice
    #cdef char[:] ret = cstr[:l]
    ## this next attempt cythonizes, but raises an exception:
    ## $ python -c 'import test; test.getbuf()'
    ## Traceback (most recent call last):
    ##   File "<string>", line 1, in <module>
    ##   File "test.pyx", line 15, in test.getbuf (test.c:1411)
    ##   File "test.pyx", line 38, in test.getbuf (test.c:1350)
    ##   File "stringsource", line 614, in View.MemoryView.memoryview_cwrapper (test.c:6763)
    ##   File "stringsource", line 321, in View.MemoryView.memoryview.__cinit__ (test.c:3309)
    ## BufferError: Object is not writable.
    cdef char[:] ret = (<const char *>cstr)[:l]
    ## this raises the same exception as above
    #cdef char[:] ret = (<char *>cstr)[:l]
    return ret

可以通过定义__getbuffer____releasebuffer__特殊方法来定义实现缓冲区协议的扩展类型。 例如:

from cpython.buffer cimport PyBuffer_FillInfo
from libc.stdlib cimport free, malloc
from libc.string cimport memcpy
cdef void dummy_function(const void **p, size_t *l):
    cdef void *tmp = malloc(17)
    memcpy(tmp, "some test bytes", 17)
    p[0] = tmp
    l[0] = 17
cdef void free_dummy_data(const void *p, size_t l, void *arg):
    free(<void *>p)
cpdef getbuf():
    cdef const void *p
    cdef size_t l
    dummy_function(&p, &l)
    return MemBuf_init(p, l, &free_dummy_data, NULL)
ctypedef void dealloc_callback(const void *p, size_t l, void *arg)
cdef class MemBuf:
    cdef const void *p
    cdef size_t l
    cdef dealloc_callback *dealloc_cb_p
    cdef void *dealloc_cb_arg
    def __getbuffer__(self, Py_buffer *view, int flags):
        PyBuffer_FillInfo(view, self, <void *>self.p, self.l, 1, flags)
    def __releasebuffer__(self, Py_buffer *view):
        pass
    def __dealloc__(self):
        if self.dealloc_cb_p != NULL:
            self.dealloc_cb_p(self.p, self.l, self.dealloc_cb_arg)
# Call this instead of constructing a MemBuf directly.  The __cinit__
# and __init__ methods can only take Python objects, so the real
# constructor is here.  See:
# https://mail.python.org/pipermail/cython-devel/2012-June/002734.html
cdef MemBuf MemBuf_init(const void *p, size_t l,
                        dealloc_callback *dealloc_cb_p,
                        void *dealloc_cb_arg):
    cdef MemBuf ret = MemBuf()
    ret.p = p
    ret.l = l
    ret.dealloc_cb_p = dealloc_cb_p
    ret.dealloc_cb_arg = dealloc_cb_arg
    return ret

使用上述(名为 test.pyx ),您将获得以下行为:

$ python -c 'import test; print repr(memoryview(test.getbuf()).tobytes())'
'some testx00 bytesx00'

我不知道是否有更简单的方法。

Python

3.3 具有PyMemoryView_FromMemory C-API 函数,该函数从提供的 C 缓冲区创建一个memoryview Python 对象。 memoryview对象确实实现了新式缓冲区接口。

如果你查看它的来源,你会发现它们相当简单。它做与PyMemoryView_FromBuffer相同的事情,除了前者用PyBuffer_FillInfo本身填充Py_buffer

既然后者存在于 Python 2.7 中,那么为什么我们不能自己调用PyBuffer_FillInfo呢?

from libc.stdlib cimport malloc
from libc.string cimport memcpy
cdef extern from "Python.h":
    ctypedef struct PyObject
    object PyMemoryView_FromBuffer(Py_buffer *view)
    int PyBuffer_FillInfo(Py_buffer *view, PyObject *obj, void *buf, Py_ssize_t len, int readonly, int infoflags)
    enum:
        PyBUF_FULL_RO
cdef void dummy_function(const void **p, size_t *l):
    cdef void *tmp = malloc(17)
    memcpy(tmp, "some test bytes", 17)
    p[0] = tmp
    l[0] = 17
cpdef getbuf():
    cdef const void *cstr
    cdef size_t l
    cdef Py_buffer buf_info
    cdef char[:] ret
    cdef int readonly
    dummy_function(&cstr, &l)
    readonly = 1
    PyBuffer_FillInfo(&buf_info, NULL, <void*>cstr, l, readonly, PyBUF_FULL_RO)
    ret = PyMemoryView_FromBuffer(&buf_info)
    return ret

但是请注意,返回值将具有如下所示的 repr:<MemoryView of 'memoryview' at 0x7f216fc70ad0> 。这是因为Cython似乎将裸露的memoryview包裹在_memoryviewslice内。由于memoryview对象已经实现了缓冲区接口,因此您可能应该简单地返回PyMemoryView_FromBuffer调用的结果。

此外,你还负责管理缓冲区的生存期。 以这种方式创建memoryview对象不会自动释放内存。您必须自己执行此操作,确保仅执行一次,没有memorybuffer引用它。在这方面,理查德·汉森的回答是更好的选择。

正如@RichardHansen在他的自我回答中正确观察到的那样,你想要的是一个实现缓冲区协议的类,并且有一个合适的析构函数来管理内存。

Cython实际上以cython.view.array的形式提供了一个相当轻量级的类,因此无需创建自己的类。它实际上记录在您链接的页面中,但为了提供一个适合您情况的快速示例:

# at the top of your file
from cython.view cimport array
# ...
# after the call to dummy_function
my_array = array(shape=(l,), itemsize=sizeof(char), format='b',  # or capital B depending on if it's signed
                 allocate_buffer=False)
my_array.data = cstr
my_array.callback_free_data = free
cdef char[:] ret = my_array

只是为了提请注意几点:allocate_buffer设置为False,因为您正在分配自己的cstr。设置callback_free_data可确保使用标准库free功能。

相关内容

  • 没有找到相关文章

最新更新