summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2019-01-07 23:23:58 +0100
committerArmin Rigo <arigo@tunes.org>2019-01-07 23:23:58 +0100
commit992922b58e02ebf0a58eb6232112da8c6fe2fdcc (patch)
tree64aeccec9ab6c99de3c138d04f9f727a3e9f642c
parentdbdef41518d844776f05adb3dfd18d589d68b704 (diff)
downloadcffi-992922b58e02ebf0a58eb6232112da8c6fe2fdcc.tar.gz
Implement ffi.from_buffer("foo[]", x)
Also contains some improvements to the documentation about other recent additions
-rw-r--r--c/_cffi_backend.c131
-rw-r--r--c/ffi_obj.c25
-rw-r--r--c/test_c.py58
-rw-r--r--cffi/api.py15
-rw-r--r--doc/source/ref.rst88
-rw-r--r--doc/source/whatsnew.rst5
-rw-r--r--testing/cffi0/test_ffi_backend.py12
-rw-r--r--testing/cffi1/test_ffi_obj.py22
-rw-r--r--testing/cffi1/test_new_ffi_1.py18
9 files changed, 263 insertions, 111 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
index 77d31f0..109b6e0 100644
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -175,7 +175,7 @@
#define CT_IS_FILE 0x00100000
#define CT_IS_VOID_PTR 0x00200000
#define CT_WITH_VAR_ARRAY 0x00400000
-#define CT_IS_UNSIZED_CHAR_A 0x00800000
+/* unused 0x00800000 */
#define CT_LAZY_FIELD_LIST 0x01000000
#define CT_WITH_PACKED_CHANGE 0x02000000
#define CT_IS_SIGNED_WCHAR 0x04000000
@@ -1870,7 +1870,7 @@ static void cdataowninggc_dealloc(CDataObject *cd)
cffi_closure_free(closure);
#endif
}
- else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */
Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
PyBuffer_Release(view);
PyObject_Free(view);
@@ -1889,7 +1889,7 @@ static int cdataowninggc_traverse(CDataObject *cd, visitproc visit, void *arg)
PyObject *args = (PyObject *)(closure->user_data);
Py_VISIT(args);
}
- else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */
Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
Py_VISIT(view->obj);
}
@@ -1911,7 +1911,7 @@ static int cdataowninggc_clear(CDataObject *cd)
closure->user_data = NULL;
Py_XDECREF(args);
}
- else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */
Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
PyBuffer_Release(view);
}
@@ -2125,7 +2125,7 @@ static PyObject *cdataowninggc_repr(CDataObject *cd)
else
return _cdata_repr2(cd, "calling", PyTuple_GET_ITEM(args, 1));
}
- else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) { /* from_buffer */
+ else if (cd->c_type->ct_flags & CT_ARRAY) { /* from_buffer */
Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
Py_ssize_t buflen = get_array_length(cd);
return PyText_FromFormat(
@@ -2369,7 +2369,7 @@ _cdata_get_indexed_ptr(CDataObject *cd, PyObject *key)
else if (cd->c_type->ct_flags & CT_ARRAY) {
if (i < 0) {
PyErr_SetString(PyExc_IndexError,
- "negative index not supported");
+ "negative index");
return NULL;
}
if (i >= get_array_length(cd)) {
@@ -2422,7 +2422,7 @@ _cdata_getslicearg(CDataObject *cd, PySliceObject *slice, Py_ssize_t bounds[])
if (ct->ct_flags & CT_ARRAY) {
if (start < 0) {
PyErr_SetString(PyExc_IndexError,
- "negative index not supported");
+ "negative index");
return NULL;
}
if (stop > get_array_length(cd)) {
@@ -3140,7 +3140,7 @@ static int explicit_release_case(PyObject *cd)
return 0;
}
else if (Py_TYPE(cd) == &CDataOwningGC_Type) {
- if (ct->ct_flags & CT_IS_UNSIZED_CHAR_A) /* ffi.from_buffer() */
+ if (ct->ct_flags & CT_ARRAY) /* ffi.from_buffer() */
return 1;
}
else if (Py_TYPE(cd) == &CDataGCP_Type) {
@@ -3309,24 +3309,24 @@ static PyTypeObject CDataOwning_Type = {
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)cdataowning_repr, /* tp_repr */
- 0, /* tp_as_number */
+ 0, /* inherited */ /* tp_as_number */
0, /* tp_as_sequence */
&CDataOwn_as_mapping, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
+ 0, /* inherited */ /* tp_hash */
+ 0, /* inherited */ /* tp_call */
0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
+ 0, /* inherited */ /* tp_getattro */
+ 0, /* inherited */ /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
+ 0, /* inherited */ /* tp_richcompare */
+ 0, /* inherited */ /* tp_weaklistoffset */
+ 0, /* inherited */ /* tp_iter */
0, /* tp_iternext */
- 0, /* tp_methods */
+ 0, /* inherited */ /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&CData_Type, /* tp_base */
@@ -3351,25 +3351,25 @@ static PyTypeObject CDataOwningGC_Type = {
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)cdataowninggc_repr, /* tp_repr */
- 0, /* tp_as_number */
+ 0, /* inherited */ /* tp_as_number */
0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
+ 0, /* inherited */ /* tp_as_mapping */
+ 0, /* inherited */ /* tp_hash */
+ 0, /* inherited */ /* tp_call */
0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
+ 0, /* inherited */ /* tp_getattro */
+ 0, /* inherited */ /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES /* tp_flags */
| Py_TPFLAGS_HAVE_GC,
0, /* tp_doc */
(traverseproc)cdataowninggc_traverse, /* tp_traverse */
(inquiry)cdataowninggc_clear, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
+ 0, /* inherited */ /* tp_richcompare */
+ 0, /* inherited */ /* tp_weaklistoffset */
+ 0, /* inherited */ /* tp_iter */
0, /* tp_iternext */
- 0, /* tp_methods */
+ 0, /* inherited */ /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&CDataOwning_Type, /* tp_base */
@@ -3393,15 +3393,15 @@ static PyTypeObject CDataGCP_Type = {
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
- 0, /* tp_repr */
- 0, /* tp_as_number */
+ 0, /* inherited */ /* tp_repr */
+ 0, /* inherited */ /* tp_as_number */
0, /* tp_as_sequence */
- 0, /* tp_as_mapping */
- 0, /* tp_hash */
- 0, /* tp_call */
+ 0, /* inherited */ /* tp_as_mapping */
+ 0, /* inherited */ /* tp_hash */
+ 0, /* inherited */ /* tp_call */
0, /* tp_str */
- 0, /* tp_getattro */
- 0, /* tp_setattro */
+ 0, /* inherited */ /* tp_getattro */
+ 0, /* inherited */ /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES /* tp_flags */
#ifdef Py_TPFLAGS_HAVE_FINALIZE
@@ -3411,11 +3411,11 @@ static PyTypeObject CDataGCP_Type = {
0, /* tp_doc */
(traverseproc)cdatagcp_traverse, /* tp_traverse */
0, /* tp_clear */
- 0, /* tp_richcompare */
- 0, /* tp_weaklistoffset */
- 0, /* tp_iter */
+ 0, /* inherited */ /* tp_richcompare */
+ 0, /* inherited */ /* tp_weaklistoffset */
+ 0, /* inherited */ /* tp_iter */
0, /* tp_iternext */
- 0, /* tp_methods */
+ 0, /* inherited */ /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&CData_Type, /* tp_base */
@@ -3427,7 +3427,7 @@ static PyTypeObject CDataGCP_Type = {
0, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
- 0, /* tp_free */
+ 0, /* inherited */ /* tp_free */
0, /* tp_is_gc */
0, /* tp_bases */
0, /* tp_mro */
@@ -3527,6 +3527,8 @@ static CDataObject *allocate_owning_object(Py_ssize_t size,
CTypeDescrObject *ct,
int dont_clear)
{
+ /* note: objects with &CDataOwning_Type are always allocated with
+ either a plain malloc() or calloc(), and freed with free(). */
CDataObject *cd;
if (dont_clear)
cd = malloc(size);
@@ -4688,9 +4690,6 @@ new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length)
sprintf(extra_text, "[]");
length = -1;
arraysize = -1;
- if ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) &&
- ctitem->ct_size == sizeof(char))
- flags |= CT_IS_UNSIZED_CHAR_A;
}
else {
sprintf(extra_text, "[%llu]", (unsigned PY_LONG_LONG)length);
@@ -6815,6 +6814,13 @@ static PyObject *direct_from_buffer(CTypeDescrObject *ct, PyObject *x,
{
CDataObject *cd;
Py_buffer *view;
+ Py_ssize_t arraylength;
+
+ if (!(ct->ct_flags & CT_ARRAY)) {
+ PyErr_Format(PyExc_TypeError, "expected an array ctype, got '%s'",
+ ct->ct_name);
+ return NULL;
+ }
/* PyPy 5.7 can obtain buffers for string (python 2)
or bytes (python 3). from_buffer(u"foo") is disallowed.
@@ -6834,6 +6840,41 @@ static PyObject *direct_from_buffer(CTypeDescrObject *ct, PyObject *x,
if (_my_PyObject_GetContiguousBuffer(x, view, require_writable) < 0)
goto error1;
+ if (ct->ct_length >= 0) {
+ /* it's an array with a fixed length; make sure that the
+ buffer contains enough bytes. */
+ if (view->len < ct->ct_size) {
+ PyErr_Format(PyExc_ValueError,
+ "buffer is too small (%zd bytes) for '%s' (%zd bytes)",
+ view->len, ct->ct_name, ct->ct_size);
+ goto error1;
+ }
+ arraylength = ct->ct_length;
+ }
+ else {
+ /* it's an open 'array[]' */
+ if (ct->ct_itemdescr->ct_size == 1) {
+ /* fast path, performance only */
+ arraylength = view->len;
+ }
+ else if (ct->ct_itemdescr->ct_size > 0) {
+ /* give it as many items as fit the buffer. Ignore a
+ partial last element. */
+ arraylength = view->len / ct->ct_itemdescr->ct_size;
+ }
+ else {
+ /* it's an array 'empty[]'. Unsupported obscure case:
+ the problem is that setting the length of the result
+ to anything large (like SSIZE_T_MAX) is dangerous,
+ because if someone tries to loop over it, it will
+ turn effectively into an infinite loop. */
+ PyErr_Format(PyExc_ZeroDivisionError,
+ "from_buffer('%s', ..): the actual length of the array "
+ "cannot be computed", ct->ct_name);
+ goto error1;
+ }
+ }
+
cd = (CDataObject *)PyObject_GC_New(CDataObject_owngc_frombuf,
&CDataOwningGC_Type);
if (cd == NULL)
@@ -6843,7 +6884,7 @@ static PyObject *direct_from_buffer(CTypeDescrObject *ct, PyObject *x,
cd->c_type = ct;
cd->c_data = view->buf;
cd->c_weakreflist = NULL;
- ((CDataObject_owngc_frombuf *)cd)->length = view->len;
+ ((CDataObject_owngc_frombuf *)cd)->length = arraylength;
((CDataObject_owngc_frombuf *)cd)->bufferview = view;
PyObject_GC_Track(cd);
return (PyObject *)cd;
@@ -6865,10 +6906,6 @@ static PyObject *b_from_buffer(PyObject *self, PyObject *args)
&require_writable))
return NULL;
- if (!(ct->ct_flags & CT_IS_UNSIZED_CHAR_A)) {
- PyErr_Format(PyExc_TypeError, "needs 'char[]', got '%s'", ct->ct_name);
- return NULL;
- }
return direct_from_buffer(ct, x, require_writable);
}
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
index 31f0135..ca1fb5f 100644
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -697,16 +697,29 @@ PyDoc_STRVAR(ffi_from_buffer_doc,
"containing large quantities of raw data in some other format, like\n"
"'array.array' or numpy arrays.");
-static PyObject *ffi_from_buffer(PyObject *self, PyObject *args, PyObject *kwds)
+static PyObject *ffi_from_buffer(FFIObject *self, PyObject *args,
+ PyObject *kwds)
{
- PyObject *arg;
+ PyObject *cdecl, *python_buf = NULL;
+ CTypeDescrObject *ct;
int require_writable = 0;
- static char *keywords[] = {"python_buffer", "require_writable", NULL};
+ static char *keywords[] = {"cdecl", "python_buffer",
+ "require_writable", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i:from_buffer", keywords,
- &arg, &require_writable))
+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:from_buffer", keywords,
+ &cdecl, &python_buf, &require_writable))
return NULL;
- return direct_from_buffer(g_ct_chararray, arg, require_writable);
+
+ if (python_buf == NULL) {
+ python_buf = cdecl;
+ ct = g_ct_chararray;
+ }
+ else {
+ ct = _ffi_type(self, cdecl, ACCEPT_STRING|ACCEPT_CTYPE);
+ if (ct == NULL)
+ return NULL;
+ }
+ return direct_from_buffer(ct, python_buf, require_writable);
}
PyDoc_STRVAR(ffi_gc_doc,
diff --git a/c/test_c.py b/c/test_c.py
index 062893f..df2143d 100644
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3753,6 +3753,64 @@ def test_from_buffer_require_writable():
p1[0] = b"g"
assert ba == b"goo"
+def test_from_buffer_types():
+ BInt = new_primitive_type("int")
+ BIntP = new_pointer_type(BInt)
+ BIntA = new_array_type(BIntP, None)
+ lst = [-12345678, 87654321, 489148]
+ bytestring = buffer(newp(BIntA, lst))[:] + b'XYZ'
+ #
+ p1 = from_buffer(BIntA, bytestring) # int[]
+ assert typeof(p1) is BIntA
+ assert len(p1) == 3
+ assert p1[0] == lst[0]
+ assert p1[1] == lst[1]
+ assert p1[2] == lst[2]
+ py.test.raises(IndexError, "p1[3]")
+ py.test.raises(IndexError, "p1[-1]")
+ #
+ py.test.raises(TypeError, from_buffer, BInt, bytestring)
+ py.test.raises(TypeError, from_buffer, BIntP, bytestring)
+ #
+ BIntA2 = new_array_type(BIntP, 2)
+ p2 = from_buffer(BIntA2, bytestring) # int[2]
+ assert typeof(p2) is BIntA2
+ assert len(p2) == 2
+ assert p2[0] == lst[0]
+ assert p2[1] == lst[1]
+ py.test.raises(IndexError, "p2[2]")
+ py.test.raises(IndexError, "p2[-1]")
+ assert p2 == p1
+ #
+ BIntA4 = new_array_type(BIntP, 4) # int[4]: too big
+ py.test.raises(ValueError, from_buffer, BIntA4, bytestring)
+ #
+ BStruct = new_struct_type("foo")
+ complete_struct_or_union(BStruct, [('a1', BInt, -1),
+ ('a2', BInt, -1)])
+ BStructP = new_pointer_type(BStruct)
+ BStructA = new_array_type(BStructP, None)
+ p1 = from_buffer(BStructA, bytestring) # struct[]
+ assert len(p1) == 1
+ assert typeof(p1) is BStructA
+ assert p1[0].a1 == lst[0]
+ assert p1[0].a2 == lst[1]
+ py.test.raises(IndexError, "p1[1]")
+ #
+ BEmptyStruct = new_struct_type("empty")
+ complete_struct_or_union(BEmptyStruct, [], Ellipsis, 0)
+ assert sizeof(BEmptyStruct) == 0
+ BEmptyStructP = new_pointer_type(BEmptyStruct)
+ BEmptyStructA = new_array_type(BEmptyStructP, None)
+ py.test.raises(ZeroDivisionError, from_buffer, # empty[]
+ BEmptyStructA, bytestring)
+ #
+ BEmptyStructA5 = new_array_type(BEmptyStructP, 5)
+ p1 = from_buffer(BEmptyStructA5, bytestring) # struct empty[5]
+ assert typeof(p1) is BEmptyStructA5
+ assert len(p1) == 5
+ assert cast(BIntP, p1) == from_buffer(BIntA, bytestring)
+
def test_memmove():
Short = new_primitive_type("short")
ShortA = new_array_type(new_pointer_type(Short), None)
diff --git a/cffi/api.py b/cffi/api.py
index 326975d..f4c4ddc 100644
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -16,6 +16,8 @@ except NameError:
# Python 3.x
basestring = str
+_unspecified = object()
+
class FFI(object):
@@ -341,15 +343,22 @@ class FFI(object):
# """
# note that 'buffer' is a type, set on this instance by __init__
- def from_buffer(self, python_buffer, require_writable=False):
- """Return a <cdata 'char[]'> that points to the data of the
+ def from_buffer(self, cdecl, python_buffer=_unspecified,
+ require_writable=False):
+ """Return a cdata of the given type pointing to the data of the
given Python object, which must support the buffer interface.
Note that this is not meant to be used on the built-in types
str or unicode (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.
+
+ The first argument is optional and default to 'char[]'.
"""
- return self._backend.from_buffer(self.BCharA, python_buffer,
+ if python_buffer is _unspecified:
+ cdecl, python_buffer = self.BCharA, cdecl
+ elif isinstance(cdecl, basestring):
+ cdecl = self._typeof(cdecl)
+ return self._backend.from_buffer(cdecl, python_buffer,
require_writable)
def memmove(self, dest, src, n):
diff --git a/doc/source/ref.rst b/doc/source/ref.rst
index 2cb4f61..e753fb6 100644
--- a/doc/source/ref.rst
+++ b/doc/source/ref.rst
@@ -60,7 +60,7 @@ the optional initializer is applied. For performance, see
`ffi.new_allocator()`_ for a way to allocate non-zero-initialized
memory.
-*New in version 1.12:* see ``ffi.release()``.
+*New in version 1.12:* see also ``ffi.release()``.
ffi.cast()
@@ -190,8 +190,8 @@ byte strings). Use ``buf[:]`` instead.
*New in version 1.10:* ``ffi.buffer`` is now the type of the returned
buffer objects; ``ffi.buffer()`` actually calls the constructor.
-**ffi.from_buffer(python_buffer, require_writable=False)**:
-return a ``<cdata 'char[]'>`` that
+**ffi.from_buffer([cdecl,] python_buffer, require_writable=False)**:
+return an array cdata (by default a ``<cdata 'char[]'>``) that
points to the data of the given Python object, which must support the
buffer interface. This is the opposite of ``ffi.buffer()``. It gives
a reference to the existing data, not a copy.
@@ -219,19 +219,33 @@ bytearray objects were supported in version 1.7 onwards (careful, if you
resize the bytearray, the ``<cdata>`` object will point to freed
memory); and byte strings were supported in version 1.8 onwards.
-*New in version 1.12:* added the ``require_writable`` argument. If set to
-True, the function fails if the buffer obtained from ``python_buffer`` is
-read-only (e.g. if ``python_buffer`` is a byte string). The exact exception is
-raised by the object itself, and for things like bytes it varies with the
-Python version, so don't rely on it. (Before version 1.12, the same effect can
-be achieved with a hack: call ``ffi.memmove(python_buffer, b"", 0)``. This has
-no effect if the object is writable, but fails if it is read-only.)
-
-Please keep in mind that CFFI does not implement the C keyword ``const``: even
-if you set ``require_writable`` to False explicitly, you still get a regular
-read-write cdata pointer.
-
-*New in version 1.12:* see ``ffi.release()``.
+*New in version 1.12:* added the optional *first* argument ``cdecl``, and
+the keyword argument ``require_writable``:
+
+* ``cdecl`` defaults to ``"char[]"``, but a different array type can be
+ specified for the result. A value like ``"int[]"`` will return an array of
+ ints instead of chars, and its length will be set to the number of ints
+ that fit in the buffer (rounded down if the division is not exact). Values
+ like ``"int[42]"`` or ``"int[2][3]"`` will return an array of exactly 42
+ (resp. 2-by-3) ints, raising a ValueError if the buffer is too small. The
+ difference between specifying ``"int[]"`` and using the older code ``p1 =
+ ffi.from_buffer(x); p2 = ffi.cast("int *", p1)`` is that the older code
+ needs to keep ``p1`` alive as long as ``p2`` is in use, because only ``p1``
+ keeps the underlying Python object alive and locked. (In addition,
+ ``ffi.from_buffer("int[]", x)`` gives better array bound checking.)
+
+* if ``require_writable`` is set to True, the function fails if the buffer
+ obtained from ``python_buffer`` is read-only (e.g. if ``python_buffer`` is
+ a byte string). The exact exception is raised by the object itself, and
+ for things like bytes it varies with the Python version, so don't rely on
+ it. (Before version 1.12, the same effect can be achieved with a hack:
+ call ``ffi.memmove(python_buffer, b"", 0)``. This has no effect if the
+ object is writable, but fails if it is read-only.) Please keep in mind
+ that CFFI does not implement the C keyword ``const``: even if you set
+ ``require_writable`` to False explicitly, you still get a regular
+ read-write cdata pointer.
+
+*New in version 1.12:* see also ``ffi.release()``.
ffi.memmove()
@@ -387,7 +401,7 @@ returned by ``ffi.new()``, the returned pointer objects have *ownership*,
which means the destructor is called as soon as *this* exact returned
object is garbage-collected.
-*New in version 1.12:* see ``ffi.release()``.
+*New in version 1.12:* see also ``ffi.release()``.
**ffi.gc(ptr, None, size=0)**:
removes the ownership on a object returned by a
@@ -569,21 +583,34 @@ add these two lines to your existing ``ffibuilder.cdef()``::
and then call these two functions manually::
- p = lib.malloc(bigsize)
+ p = lib.malloc(n * ffi.sizeof("int"))
try:
- my_array = ffi.cast("some_other_type_than_void*", p)
+ my_array = ffi.cast("int *", p)
...
finally:
lib.free(p)
+In cffi version 1.12 you can indeed use ``ffi.new_allocator()`` but use the
+``with`` statement (see ``ffi.release()``) to force the free function to be
+called at a known point. The above is equivalent to this code::
+
+ my_new = ffi.new_allocator(lib.malloc, lib.free) # at global level
+ ...
+ with my_new("int[]", n) as my_array:
+ ...
-ffi.release()
-+++++++++++++
-**ffi.release(cdata)**: release now the resources held by a cdata object from
- ``ffi.new()``, ``ffi.gc()``, ``ffi.from_buffer()`` or
- ``ffi.new_allocator()()``. The cdata object must not be used afterwards.
- *New in version 1.12.*
+.. _ffi-release:
+
+ffi.release() and the context manager
++++++++++++++++++++++++++++++++++++++
+
+**ffi.release(cdata)**: release the resources held by a cdata object from
+``ffi.new()``, ``ffi.gc()``, ``ffi.from_buffer()`` or
+``ffi.new_allocator()()``. The cdata object must not be used afterwards.
+The regular destructor of the cdata object releases the same resources,
+but this allows the operation to occur at a known time.
+*New in version 1.12.*
``ffi.release(cdata)`` is equivalent to ``cdata.__exit__()``, which means that
you can use the ``with`` statement to ensure that the cdata is released at the
@@ -592,6 +619,8 @@ end of a block (in version 1.12 and above)::
with ffi.from_buffer(...) as p:
do something with p
+The effect is more precisely as follows:
+
* on an object returned from ``ffi.gc(destructor)``, ``ffi.release()`` will
cause the ``destructor`` to be called immediately.
@@ -599,8 +628,8 @@ end of a block (in version 1.12 and above)::
is called immediately.
* on CPython, ``ffi.from_buffer(buf)`` locks the buffer, so ``ffi.release()``
- unlocks it at a deterministic point. On PyPy, there is no locking (so far)
- so this has no effect.
+ can be used to unlock it at a known time. On PyPy, there is no locking
+ (so far) so this has no effect.
* on CPython this method has no effect (so far) on objects returned by
``ffi.new()``, because the memory is allocated inline with the cdata object
@@ -615,6 +644,11 @@ end of a block (in version 1.12 and above)::
anyway; but calling ``ffi.release()`` explicitly should improve performance
by reducing the frequency of GC runs.
+After ``ffi.release(x)``, do not use anything pointed to by ``x`` any longer.
+As an exception to this rule, you can call ``ffi.release(x)`` several times
+for the exact same cdata object ``x``; the calls after the first one are
+ignored.
+
ffi.init_once()
+++++++++++++++
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
index 9ebdaa1..4610afd 100644
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -23,8 +23,9 @@ v1.12
* CPython 2.x: ``ffi.dlopen()`` failed with non-ascii file names on Posix
-* ``ffi.from_buffer()`` takes a new keyword argument ``require_writable``.
- When set to True, it asks the object passed in to raise an exception if
+* ``ffi.from_buffer()`` takes two new arguments: an optional *first* argument
+ gives the array type of the result; and the keyword argument
+ ``require_writable`` can ask the object passed in to raise an exception if
it is read-only.
* ``ffi.new()``, ``ffi.gc()`` or ``ffi.from_buffer()`` cdata objects
diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py
index 4b43074..12ecaee 100644
--- a/testing/cffi0/test_ffi_backend.py
+++ b/testing/cffi0/test_ffi_backend.py
@@ -324,16 +324,22 @@ class TestBitfield:
a = array.array('H', [10000, 20000, 30000])
c = ffi.from_buffer(a)
assert ffi.typeof(c) is ffi.typeof("char[]")
+ assert len(c) == 6
ffi.cast("unsigned short *", c)[1] += 500
assert list(a) == [10000, 20500, 30000]
- assert c == ffi.from_buffer(a, True)
+ assert c == ffi.from_buffer("char[]", a, True)
assert c == ffi.from_buffer(a, require_writable=True)
#
+ c = ffi.from_buffer("unsigned short[]", a)
+ assert len(c) == 3
+ assert c[1] == 20500
+ #
p = ffi.from_buffer(b"abcd")
assert p[2] == b"c"
#
- assert p == ffi.from_buffer(b"abcd", False)
- py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
+ assert p == ffi.from_buffer(b"abcd", require_writable=False)
+ py.test.raises((TypeError, BufferError), ffi.from_buffer,
+ "char[]", b"abcd", True)
py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
require_writable=True)
diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py
index 4222740..e07d6f9 100644
--- a/testing/cffi1/test_ffi_obj.py
+++ b/testing/cffi1/test_ffi_obj.py
@@ -238,19 +238,31 @@ def test_ffi_buffer():
def test_ffi_from_buffer():
import array
ffi = _cffi1_backend.FFI()
- a = array.array('H', [10000, 20000, 30000])
+ a = array.array('H', [10000, 20000, 30000, 40000])
c = ffi.from_buffer(a)
assert ffi.typeof(c) is ffi.typeof("char[]")
+ assert len(c) == 8
ffi.cast("unsigned short *", c)[1] += 500
- assert list(a) == [10000, 20500, 30000]
- assert c == ffi.from_buffer(a, True)
+ assert list(a) == [10000, 20500, 30000, 40000]
+ py.test.raises(TypeError, ffi.from_buffer, a, True)
+ assert c == ffi.from_buffer("char[]", a, True)
assert c == ffi.from_buffer(a, require_writable=True)
#
+ c = ffi.from_buffer("unsigned short[]", a)
+ assert len(c) == 4
+ assert c[1] == 20500
+ #
+ c = ffi.from_buffer("unsigned short[2][2]", a)
+ assert len(c) == 2
+ assert len(c[0]) == 2
+ assert c[0][1] == 20500
+ #
p = ffi.from_buffer(b"abcd")
assert p[2] == b"c"
#
- assert p == ffi.from_buffer(b"abcd", False)
- py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
+ assert p == ffi.from_buffer(b"abcd", require_writable=False)
+ py.test.raises((TypeError, BufferError), ffi.from_buffer,
+ "char[]", b"abcd", True)
py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
require_writable=True)
diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py
index daa0c15..209cb30 100644
--- a/testing/cffi1/test_new_ffi_1.py
+++ b/testing/cffi1/test_new_ffi_1.py
@@ -1675,24 +1675,6 @@ class TestNewFFI1:
py.test.raises(TypeError, len, q.a)
py.test.raises(TypeError, list, q.a)
- def test_from_buffer(self):
- import array
- 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]
- assert c == ffi.from_buffer(a, True)
- assert c == ffi.from_buffer(a, require_writable=True)
- #
- p = ffi.from_buffer(b"abcd")
- assert p[2] == b"c"
- #
- assert p == ffi.from_buffer(b"abcd", False)
- py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
- py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
- require_writable=True)
-
def test_all_primitives(self):
assert set(PRIMITIVE_TO_INDEX) == set([
"char",