Author: Armin Rigo <[email protected]>
Branch:
Changeset: r1604:cda48f686feb
Date: 2014-12-28 06:12 +0100
http://bitbucket.org/cffi/cffi/changeset/cda48f686feb/
Log: Fight a lot the CPython buffer/memoryview interface until we get a
hopefully not-too-buggy ffi.from_buffer(). Obviously it's only
half-tested because 2.7 doesn't really have built-in memoryview
objects.
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -86,7 +86,7 @@
# define PyText_FromStringAndSize PyString_FromStringAndSize
# define PyText_InternInPlace PyString_InternInPlace
# define PyIntOrLong_Check(op) (PyInt_Check(op) || PyLong_Check(op))
-#endif
+#endif
#if PY_MAJOR_VERSION >= 3
# define PyInt_FromLong PyLong_FromLong
@@ -130,6 +130,7 @@
#define CT_IS_FILE 262144
#define CT_IS_VOID_PTR 524288
#define CT_WITH_VAR_ARRAY 1048576
+#define CT_IS_UNSIZED_CHAR_A 2097152
#define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \
CT_PRIMITIVE_UNSIGNED | \
CT_PRIMITIVE_CHAR | \
@@ -224,6 +225,12 @@
} CDataObject_own_structptr;
typedef struct {
+ CDataObject head;
+ Py_ssize_t length; /* same as CDataObject_own_length up to here */
+ Py_buffer *bufferview;
+} CDataObject_owngc_frombuf;
+
+typedef struct {
ffi_cif cif;
/* the following information is used when doing the call:
- a buffer of size 'exchange_size' is malloced
@@ -1546,6 +1553,11 @@
Py_XDECREF(args);
cffi_closure_free(closure);
}
+ else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
+ PyBuffer_Release(view);
+ PyObject_Free(view);
+ }
cdata_dealloc(cd);
}
@@ -1560,6 +1572,10 @@
PyObject *args = (PyObject *)(closure->user_data);
Py_VISIT(args);
}
+ else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
+ Py_VISIT(view->obj);
+ }
return 0;
}
@@ -1577,6 +1593,10 @@
closure->user_data = NULL;
Py_XDECREF(args);
}
+ else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
+ PyBuffer_Release(view);
+ }
return 0;
}
@@ -1679,35 +1699,40 @@
static PyObject *cdataowning_repr(CDataObject *cd)
{
Py_ssize_t size;
- if (cd->c_type->ct_flags & CT_POINTER) {
- if (cd->c_type->ct_flags & CT_IS_VOID_PTR)
- goto handle_repr;
+ if (cd->c_type->ct_flags & CT_POINTER)
size = cd->c_type->ct_itemdescr->ct_size;
- }
else if (cd->c_type->ct_flags & CT_ARRAY)
size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size;
- else if (cd->c_type->ct_flags & CT_FUNCTIONPTR)
- goto callback_repr;
else
size = cd->c_type->ct_size;
return PyText_FromFormat("<cdata '%s' owning %zd bytes>",
cd->c_type->ct_name, size);
-
- callback_repr:
- {
+}
+
+static PyObject *cdataowninggc_repr(CDataObject *cd)
+{
+ if (cd->c_type->ct_flags & CT_IS_VOID_PTR) { /* a handle */
+ PyObject *x = (PyObject *)(cd->c_data + 42);
+ return _cdata_repr2(cd, "handle to", x);
+ }
+ else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */
PyObject *args = (PyObject *)((ffi_closure *)cd->c_data)->user_data;
if (args == NULL)
return cdata_repr(cd);
else
return _cdata_repr2(cd, "calling", PyTuple_GET_ITEM(args, 1));
}
-
- handle_repr:
- {
- PyObject *x = (PyObject *)(cd->c_data + 42);
- return _cdata_repr2(cd, "handle to", x);
- }
+ else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
+ Py_ssize_t buflen = get_array_length(cd);
+ return PyText_FromFormat(
+ "<cdata '%s' buffer len %zd from '%.200s' object>",
+ cd->c_type->ct_name,
+ buflen,
+ view->obj ? Py_TYPE(view->obj)->tp_name : "(null)");
+ }
+ return cdataowning_repr(cd);
}
static int cdata_nonzero(CDataObject *cd)
@@ -2397,7 +2422,7 @@
ct = ((CDataObject *)obj)->c_type;
if (ct->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_UNSIGNED |
CT_PRIMITIVE_SIGNED)) {
- if (ct->ct_size < sizeof(int)) {
+ if (ct->ct_size < (Py_ssize_t)sizeof(int)) {
ct = _get_ct_int();
if (ct == NULL)
goto error;
@@ -2626,14 +2651,14 @@
static PyTypeObject CDataOwningGC_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"_cffi_backend.CDataOwnGC",
- sizeof(CDataObject),
+ sizeof(CDataObject_owngc_frombuf),
0,
(destructor)cdataowninggc_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
- 0, /* tp_repr */
+ (reprfunc)cdataowninggc_repr, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
@@ -3454,11 +3479,11 @@
td->ct_extra = ffitype;
td->ct_flags = ptypes->flags;
if (td->ct_flags & (CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_CHAR)) {
- if (td->ct_size <= sizeof(long))
+ if (td->ct_size <= (Py_ssize_t)sizeof(long))
td->ct_flags |= CT_PRIMITIVE_FITS_LONG;
}
else if (td->ct_flags & CT_PRIMITIVE_UNSIGNED) {
- if (td->ct_size < sizeof(long))
+ if (td->ct_size < (Py_ssize_t)sizeof(long))
td->ct_flags |= CT_PRIMITIVE_FITS_LONG;
}
td->ct_name_position = strlen(td->ct_name);
@@ -3521,6 +3546,7 @@
CTypeDescrObject *td, *ctitem;
char extra_text[32];
Py_ssize_t length, arraysize;
+ int flags = CT_ARRAY;
if (!(ctptr->ct_flags & CT_POINTER)) {
PyErr_SetString(PyExc_TypeError, "first arg must be a pointer ctype");
@@ -3537,6 +3563,9 @@
sprintf(extra_text, "[]");
length = -1;
arraysize = -1;
+ if ((ctptr->ct_flags & CT_CAST_ANYTHING) &&
+ (ctitem->ct_flags & CT_PRIMITIVE_CHAR))
+ flags |= CT_IS_UNSIZED_CHAR_A;
}
else {
length = PyNumber_AsSsize_t(lengthobj, PyExc_OverflowError);
@@ -3561,7 +3590,7 @@
td->ct_stuff = (PyObject *)ctptr;
td->ct_size = arraysize;
td->ct_length = length;
- td->ct_flags = CT_ARRAY;
+ td->ct_flags = flags;
return (PyObject *)td;
}
@@ -5081,6 +5110,96 @@
return x;
}
+static int _my_PyObject_GetContiguousBuffer(PyObject *x, Py_buffer *view)
+{
+#if PY_MAJOR_VERSION < 3
+ /* Some objects only support the buffer interface and CPython doesn't
+ translate it into the memoryview interface, mess. Hack a very
+ minimal content for 'view'. Don't care if the other fields are
+ uninitialized: we only call PyBuffer_Release(), which only reads
+ 'view->obj'. */
+ PyBufferProcs *pb = x->ob_type->tp_as_buffer;
+ if (pb && !pb->bf_releasebuffer) {
+ /* try all three in some vaguely sensible order */
+ readbufferproc proc = (readbufferproc)pb->bf_getwritebuffer;
+ if (!proc) proc = (readbufferproc)pb->bf_getcharbuffer;
+ if (!proc) proc = (readbufferproc)pb->bf_getreadbuffer;
+ if (proc && pb->bf_getsegcount) {
+ if ((*pb->bf_getsegcount)(x, NULL) != 1) {
+ PyErr_SetString(PyExc_TypeError,
+ "expected a single-segment buffer object");
+ return -1;
+ }
+ view->len = (*proc)(x, 0, &view->buf);
+ if (view->len < 0)
+ return -1;
+ view->obj = x;
+ Py_INCREF(x);
+ return 0;
+ }
+ }
+#endif
+
+ if (PyObject_GetBuffer(x, view, PyBUF_SIMPLE) < 0)
+ return -1;
+
+ if (!PyBuffer_IsContiguous(view, 'A')) {
+ PyBuffer_Release(view);
+ PyErr_SetString(PyExc_TypeError, "contiguous buffer expected");
+ return -1;
+ }
+ return 0;
+}
+
+static PyObject *b_from_buffer(PyObject *self, PyObject *args)
+{
+ CTypeDescrObject *ct;
+ CDataObject *cd;
+ PyObject *x;
+ Py_buffer *view;
+
+ if (!PyArg_ParseTuple(args, "O!O", &CTypeDescr_Type, &ct, &x))
+ return NULL;
+
+ if (!(ct->ct_flags & CT_IS_UNSIZED_CHAR_A)) {
+ PyErr_Format(PyExc_TypeError, "needs 'char[]', got '%s'", ct->ct_name);
+ return NULL;
+ }
+
+ if (PyString_Check(x) || PyUnicode_Check(x) ||
+ PyByteArray_Check(x) /* <= this one here for PyPy compatibility */ ) {
+ PyErr_SetString(PyExc_TypeError,
+ "from_buffer() cannot return the address of the "
+ "raw string within a "STR_OR_BYTES" or unicode or "
+ "bytearray object");
+ return NULL;
+ }
+
+ view = PyObject_Malloc(sizeof(Py_buffer));
+ if (_my_PyObject_GetContiguousBuffer(x, view) < 0)
+ goto error1;
+
+ cd = (CDataObject *)PyObject_GC_New(CDataObject_owngc_frombuf,
+ &CDataOwningGC_Type);
+ if (cd == NULL)
+ goto error2;
+
+ Py_INCREF(ct);
+ cd->c_type = ct;
+ cd->c_data = view->buf;
+ cd->c_weakreflist = NULL;
+ ((CDataObject_owngc_frombuf *)cd)->length = view->len;
+ ((CDataObject_owngc_frombuf *)cd)->bufferview = view;
+ PyObject_GC_Track(cd);
+ return (PyObject *)cd;
+
+ error2:
+ PyBuffer_Release(view);
+ error1:
+ PyObject_Free(view);
+ return NULL;
+}
+
static PyObject *b__get_types(PyObject *self, PyObject *noarg)
{
return PyTuple_Pack(2, (PyObject *)&CData_Type,
@@ -5326,6 +5445,7 @@
{"set_errno", b_set_errno, METH_VARARGS},
{"newp_handle", b_newp_handle, METH_VARARGS},
{"from_handle", b_from_handle, METH_O},
+ {"from_buffer", b_from_buffer, METH_VARARGS},
#ifdef MS_WIN32
{"getwinerror", b_getwinerror, METH_VARARGS},
#endif
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3206,6 +3206,19 @@
('a2', BChar, 5)],
None, -1, -1, SF_PACKED)
+def test_from_buffer():
+ import array
+ a = array.array('H', [10000, 20000, 30000])
+ BChar = new_primitive_type("char")
+ BCharP = new_pointer_type(BChar)
+ BCharA = new_array_type(BCharP, None)
+ c = from_buffer(BCharA, a)
+ assert typeof(c) is BCharA
+ assert repr(c) == "<cdata 'char[]' buffer len 6 from 'array.array' object>"
+ p = new_pointer_type(new_primitive_type("unsigned short"))
+ cast(p, c)[1] += 500
+ assert list(a) == [10000, 20500, 30000]
+
def test_version():
# this test is here mostly for PyPy
assert __version__ == "0.8.6"
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -77,6 +77,7 @@
#
with self._lock:
self.BVoidP = self._get_cached_btype(model.voidp_type)
+ self.BCharA = self._get_cached_btype(model.char_array_type)
if isinstance(backend, types.ModuleType):
# _cffi_backend: attach these constants to the class
if not hasattr(FFI, 'NULL'):
@@ -264,6 +265,16 @@
"""
return self._backend.buffer(cdata, size)
+ def from_buffer(self, python_buffer):
+ """Return a <cdata 'void *'> that points to the data of the
+ given object, which must support the buffer interface. Note
+ that this is not meant to be used on the built-in types str,
+ unicode, or bytearray (you can build 'char[]' arrays explicitly)
+ but only on objects containing large quantities of raw data
+ in some other format, like 'array.array' or numpy arrays.
+ """
+ return self._backend.from_buffer(self.BCharA, python_buffer)
+
def callback(self, cdecl, python_callable=None, error=None):
"""Return a callback object or a decorator making such a
callback object. 'cdecl' must name a C function pointer type.
diff --git a/cffi/model.py b/cffi/model.py
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -235,6 +235,8 @@
BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist)
return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length)
+char_array_type = ArrayType(PrimitiveType('char'), None)
+
class StructOrUnionOrEnum(BaseTypeByIdentity):
_attrs_ = ('name',)
diff --git a/testing/test_cdata.py b/testing/test_cdata.py
--- a/testing/test_cdata.py
+++ b/testing/test_cdata.py
@@ -19,6 +19,8 @@
return FakeType("void")
def new_pointer_type(self, x):
return FakeType('ptr-to-%r' % (x,))
+ def new_array_type(self, x, y):
+ return FakeType('array-from-%r-len-%r' % (x, y))
def cast(self, x, y):
return 'casted!'
def _get_types(self):
diff --git a/testing/test_ffi_backend.py b/testing/test_ffi_backend.py
--- a/testing/test_ffi_backend.py
+++ b/testing/test_ffi_backend.py
@@ -213,3 +213,12 @@
code, message = ffi.getwinerror(-1)
assert code == 2
assert message == "The system cannot find the file specified"
+
+ def test_from_buffer(self):
+ import array
+ ffi = FFI()
+ a = array.array('H', [10000, 20000, 30000])
+ c = ffi.from_buffer(a)
+ assert ffi.typeof(c) is ffi.typeof("char[]")
+ ffi.cast("unsigned short *", c)[1] += 500
+ assert list(a) == [10000, 20500, 30000]
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit