Search code examples
pythonccythonpython-cffi

Buffer protocol using CFFI


I want to expose a buffer protocol for a object, just like in this example of the Cython documentation, however I need to do this using CFFI and I wasn't able to find any examples to expose a buffer protocol.


Solution

  • My reading of the question is that you have some data you've got from a CFFI interface and want to expose it using the standard Python buffer protocol (which lots of C extensions use for quick access to array data).

    The good news ffi.buffer() command (which, in fairness, I didn't know about until OP mentioned it!) exposes both a Python interface and the C-API side buffer protocol. It is restricted to viewing the data as an unsigned char/byte array though. Fortunately, using other Python objects (e.g. a memoryview it's possible to view it as other types).

    Remainder of the post is an illustrative example:

    # buf_test.pyx
    # This is just using Cython to define a couple of functions that expect
    # objects with the buffer protocol of different types, as an easy way
    # to prove it works. Cython isn't needed to use ffi.buffer()!
    def test_uchar(unsigned char[:] contents):
        print(contents.shape[0])
        for i in range(contents.shape[0]):
            contents[i]=b'a'
    
    def test_double(double[:] contents):
        print(contents.shape[0])
        for i in range(contents.shape[0]):
            contents[i]=1.0
    

    ... and the Python file using cffi

    import cffi
    ffi = cffi.FFI()
    
    data = ffi.buffer(ffi.new("double[20]")) # allocate some space to store data
             # alternatively, this could have been returned by a function wrapped
             # using ffi
    
    # now use the Cython file to test the buffer interface
    import pyximport; pyximport.install()
    import buf_test
    
    # next line DOESN'T WORK - complains about the data type of the buffer
    # buf_test.test_double(obj.data) 
    
    buf_test.test_uchar(obj.data) # works fine - but interprets as unsigned char
    
    # we can also use casts and the Python
    # standard memoryview object to get it as a double array
    buf_test.test_double(memoryview(obj.data).cast('d'))