[pypy-commit] cffi default: Replace ffi.string() with ffi.buffer() and update the docs. Add an

arigo noreply at buildbot.pypy.org
Fri Jun 15 16:29:19 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r366:c8cefb96f807
Date: 2012-06-15 16:29 +0200
http://bitbucket.org/cffi/cffi/changeset/c8cefb96f807/

Log:	Replace ffi.string() with ffi.buffer() and update the docs. Add an
	example of how to use it.

diff --git a/c/_ffi_backend.c b/c/_ffi_backend.c
--- a/c/_ffi_backend.c
+++ b/c/_ffi_backend.c
@@ -3110,23 +3110,31 @@
     return PyInt_FromSsize_t(cf->cf_offset);
 }
 
-static PyObject *b_string(PyObject *self, PyObject *args)
+static PyObject *b_buffer(PyObject *self, PyObject *args)
 {
     CDataObject *cd;
-    CTypeDescrObject *ct;
     Py_ssize_t length;
-    if (!PyArg_ParseTuple(args, "O!n:string",
-                          &CData_Type, &cd, &length))
+    if (!PyArg_ParseTuple(args, "O!:buffer",
+                          &CData_Type, &cd))
         return NULL;
-    ct = cd->c_type;
-    if (!(ct->ct_flags & CT_POINTER) ||
-            !(ct->ct_itemdescr->ct_flags & CT_CAST_ANYTHING)) {
+
+    if (cd->c_type->ct_flags & CT_POINTER)
+        length = cd->c_type->ct_size;
+    else if (cd->c_type->ct_flags & CT_ARRAY)
+        length = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size;
+    else {
         PyErr_Format(PyExc_TypeError,
-                     "expected a cdata 'char *' or 'void *', got '%s'",
-                     ct->ct_name);
+                     "expected a pointer or array cdata, got '%s'",
+                     cd->c_type->ct_name);
         return NULL;
     }
-    return PyString_FromStringAndSize(cd->c_data, length);
+    if (length < 0) {
+        PyErr_Format(PyExc_TypeError,
+                     "don't know the size pointed to by '%s'",
+                     cd->c_type->ct_name);
+        return NULL;
+    }
+    return PyBuffer_FromReadWriteMemory(cd->c_data, length);
 }
 
 static PyObject *b_get_errno(PyObject *self, PyObject *noarg)
@@ -3269,7 +3277,7 @@
     {"sizeof", b_sizeof, METH_O},
     {"typeof", b_typeof, METH_O},
     {"offsetof", b_offsetof, METH_VARARGS},
-    {"string", b_string, METH_VARARGS},
+    {"buffer", b_buffer, METH_VARARGS},
     {"get_errno", b_get_errno, METH_NOARGS},
     {"set_errno", b_set_errno, METH_VARARGS},
     {"gc", b_gc, METH_VARARGS},
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -160,12 +160,13 @@
         BType = self.typeof(cdecl)
         return self._backend.cast(BType, source)
 
-    def string(self, pointer, length):
-        """Return a Python string containing the data at the given
-        raw pointer with the given size.  The pointer must be a
-        <cdata 'void *'> or <cdata 'char *'>.
+    def buffer(self, cdata):
+        """Return a read-write buffer object that references the raw C data
+        pointed to by the given 'cdata'.  The 'cdata' must be a pointer or
+        an array.  To get a copy of it in a regular string, call str() on
+        the result.
         """
-        return self._backend.string(pointer, length)
+        return self._backend.buffer(pointer)
 
     def callback(self, cdecl, python_callable):
         if not callable(python_callable):
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -760,12 +760,19 @@
     def set_errno(self, value):
         ctypes.set_errno(value)
 
-    def string(self, bptr, length):
-        if not (isinstance(bptr, CTypesGenericPtr) and bptr._automatic_casts):
+    def buffer(self, bptr):
+        # haaaaaaaaaaaack
+        call = ctypes.pythonapi.PyBuffer_FromReadWriteMemory
+        call.argtypes = (ctypes.c_void_p, ctypes.c_size_t)
+        call.restype = ctypes.py_object
+        #
+        if isinstance(bptr, CTypesGenericPtr):
+            return call(bptr._as_ctype_ptr, bptr._bitem_size)
+        elif isinstance(bptr, CTypesGenericArray):
+            return call(ctypes.pointer(bptr._blob), ctypes.sizeof(bptr._blob))
+        else:
             raise TypeError("'void *' argument expected, got %r" %
                             (type(bptr).__name__,))
-        p = ctypes.cast(bptr._as_ctype_ptr, ctypes.POINTER(ctypes.c_char))
-        return ''.join([p[i] for i in range(length)])
 
     def sizeof(self, cdata_or_BType):
         if isinstance(cdata_or_BType, CTypesData):
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -124,6 +124,10 @@
     image[0].g = 192
     image[0].b = 128
 
+    f = open('data', 'wb')
+    f.write(ffi.buffer(image))
+    f.close()
+
 This can be used as a more flexible replacement of the struct_ and
 array_ modules.  You could also call ``ffi.new("pixel_t[600][800]")``
 and get a two-dimensional array.
@@ -480,11 +484,10 @@
 library.  It can also be accessed more directly via reads and writes of
 ``ffi.errno``.
 
-``ffi.string(pointer, length)``: return a Python string containing all
-the data at the given location with the given size.  The pointer must be
-a cdata of type ``void *`` or ``char *``.  Null characters are not
-considered special here: the resulting string always has the given
-``length``, possibly with embedded null characters.
+``ffi.buffer(pointer)``: return a read-write buffer object that
+references the raw C data pointed to by the given 'cdata'.  The 'cdata'
+must be a pointer or an array.  To get a copy of it in a regular string,
+call str() on the result.
 
 ``ffi.typeof("C type" or cdata object)``: return an object of type
 ``<ctype>`` corresponding to the parsed string, or to the C type of the
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -757,15 +757,32 @@
         assert p == s+0
         assert p+1 == s+1
 
-    def test_ffi_string(self):
+    def test_ffi_buffer_ptr(self):
+        ffi = FFI(backend=self.Backend())
+        a = ffi.new("int", 100)
+        b = ffi.buffer(a)
+        assert type(b) is buffer
+        if sys.byteorder == 'little':
+            assert str(b) == '\x64\x00\x00\x00'
+            b[0] = '\x65'
+        else:
+            assert str(b) == '\x00\x00\x00\x64'
+            b[3] = '\x65'
+        assert a[0] == 101
+
+    def test_ffi_buffer_array(self):
         ffi = FFI(backend=self.Backend())
         a = ffi.new("int[]", range(100, 110))
-        s = ffi.string(ffi.cast("void *", a), 8)
-        assert type(s) is str
+        b = ffi.buffer(a)
+        assert type(b) is buffer
         if sys.byteorder == 'little':
-            assert s == '\x64\x00\x00\x00\x65\x00\x00\x00'
+            assert str(b).startswith('\x64\x00\x00\x00\x65\x00\x00\x00')
+            b[4] = '\x45'
         else:
-            assert s == '\x00\x00\x00\x64\x00\x00\x00\x65'
+            assert str(b).startswith('\x00\x00\x00\x64\x00\x00\x00\x65')
+            b[7] = '\x45'
+        assert len(str(b)) == 4 * 10
+        assert a[1] == 0x45
 
     def test_new_struct_containing_array_varsize(self):
         py.test.skip("later?")


More information about the pypy-commit mailing list