2015-01-26 2 views
7

Я пишу модуль расширения Python 2.7 в Китоне. Как создать объект Python, реализующий интерфейс буфера нового стиля, который обертывает кусок памяти, предоставленный мне библиотекой C? Блок памяти - это всего лишь строка байтов, а не структура или многомерный массив. Мне дается указатель const void * и длина, а также некоторые сведения о том, как долго указатель остается действительным.Как обернуть указатель и длину C в объект буфера нового стиля в Cython?

Я не могу скопировать память - это убьет производительность для моего приложения.

С объектами буфера старого стиля я мог бы просто использовать PyBuffer_FromMemory(), но я не могу найти такой же простой способ создания объекта буфера нового типа.

Должен ли я создать свой собственный класс, который реализует интерфейс буфера? Или Cython предоставляет простой способ сделать это?

Я читал Unicode and Passing Strings и Typed Memoryviews страниц из документации Cython, но документация неточна и не очень полная, и нет примеров, похожих на то, что я хочу сделать.

Вот что я пытался (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 
+0

Возможно, это неудачно, потому что вы используете 'const char *' вместо 'char *'? – Kevin

+0

@Kevin: Я обновил свой вопрос, чтобы указать, что такое же исключение происходит, даже если я передал 'char *' вместо 'const char *'. Спасибо что подметил это. –

+1

Изучив проблему более подробно, я хотел бы указать, что memcpy является незаконным. Вы объявили 'tmp' как const, а затем изменили его. Это неопределенное поведение по стандарту C. Поскольку вы также сказали, что пытаетесь избежать копирования памяти, я немного запутался в этом вопросе. – Kevin

ответ

5

Вы можете определить extension type, который реализует протокол буфера путем определения __getbuffer__ и __releasebuffer__special methods. Например:

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\0 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 test\x00 bytes\x00' 

Я не знаю, если есть более простой способ.

+0

'MemBuf' создает утечку памяти. '__releasebuffer__' должен вызывать' PyBuffer_Release (view) '. Вероятно, вы должны написать функцию '__dealloc__' для' MemBuf', которая вызывает 'free', если она владеет памятью, которая была возвращена функцией C. – Dunes

+0

@ Dunes: Да, вы правы. Я обновил свой ответ, чтобы освободить память в '__dealloc__'. В моем реальном коде функция C сохраняет право собственности на блок памяти, поэтому я не думал освобождать память в этом примере кода. –

1

Python 3.3 имеет PyMemoryView_FromMemory Функция C-API, которая создает объект Python memoryview из поставляемого буфера C. memoryview объекты действительно реализуют интерфейс буфера нового стиля.

Если вы посмотрите на its sources, вы заметите, что они довольно просты. Он делает то же самое, что и PyMemoryView_FromBuffer, за исключением бывшего заполняет Py_buffer с PyBuffer_FillInfo.

Поскольку последний существует в 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\0 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 

Обратите внимание, что, однако, что возвращаемое значение будет иметь магнезии, который выглядит следующим образом: <MemoryView of 'memoryview' at 0x7f216fc70ad0>. Это потому, что Cython, кажется, обнажает memoryview внутри _memoryviewslice. Поскольку объекты memoryview уже реализуют интерфейс буфера, скорее всего, вы должны просто вернуть результат вызова PyMemoryView_FromBuffer.

Кроме того, вы отвечаете за управление временем жизни вашего буфера. memoryview Объекты, созданные таким образом, автоматически не освободят память. Вы должны сделать это сами, гарантируя, что вы это сделаете только после того, как memorybuffer ссылается на него. В этой связи ответ Ричарда Хансена намного лучше.