pythonpython-2.7cythonpython-c-extensionmemoryview

How to wrap a C pointer and length in a new-style buffer object in Cython?


I'm writing a Python 2.7 extension module in Cython. How do I create a Python object implementing the new-style buffer interface that wraps a chunk of memory given to me by a C library? The chunk of memory is just a string of bytes, not a structure or multidimensional array. I'm given a const void * pointer and a length, and some details about how long the pointer stays valid.

I can't copy the memory—that would kill performance for my application.

With the old-style buffer objects I could simply use PyBuffer_FromMemory(), but I can't seem to find a similarly easy way to produce a new-style buffer object.

Do I have to create my own class that implements the buffer interface? Or does Cython provide an easy way to do this?

I've read the Unicode and Passing Strings and Typed Memoryviews pages from the Cython documentation, but the documentation is imprecise and not very complete and there are no examples that look similar to what I want to do.

Here's what I've tried (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\0 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

Solution

  • As @RichardHansen correctly observes in his self-answer, what you want is a class that implements the buffer protocol, and has a suitable destructor that manages the memory.

    Cython actually provides a fairly lightweight class built into it in the form of cython.view.array so there's no need to create your own. It's actually documented in the page you linked but for the sake of providing a quick example that fits your case:

    # 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
    

    Just to draw attention to a couple of bits: allocate_buffer is set to False since you're allocating your own in cstr. Setting callback_free_data ensures that the standard library free function is used.