Commits

Armin Rigo committed 57fb7d3

Remove a special case from _cffi_backend, and replace it with a general
solution: ffi.addressof(), only for structs or unions.

  • Participants
  • Parent commits 0ee9430

Comments (0)

Files changed (6)

File c/_cffi_backend.c

             }
             ((char **)data)[1] = NULL;
         }
-        if (convert_from_object(data, argtype, obj) < 0) {
-            if (CData_Check(obj) && (argtype->ct_flags & CT_IS_PTR_TO_OWNED) &&
-                   argtype->ct_itemdescr == ((CDataObject *)obj)->c_type) {
-                /* special case to make the life of verifier.py easier:
-                   if the formal argument type is 'struct foo *' but
-                   we pass a 'struct foo', then get a pointer to it */
-                PyErr_Clear();
-                ((char **)data)[0] = ((CDataObject *)obj)->c_data;
-                continue;
-            }
+        if (convert_from_object(data, argtype, obj) < 0)
             goto error;
-        }
     }
 
     resultdata = buffer + cif_descr->exchange_offset_arg[0];
     return PyInt_FromSsize_t(cf->cf_offset);
 }
 
+static PyObject *b_addressof(PyObject *self, PyObject *args)
+{
+    CTypeDescrObject *ct;
+    CDataObject *cd;
+
+    if (!PyArg_ParseTuple(args, "O!O!:addressof",
+                          &CTypeDescr_Type, &ct,
+                          &CData_Type, &cd))
+        return NULL;
+
+    if ((cd->c_type->ct_flags & (CT_STRUCT|CT_UNION)) == 0) {
+        PyErr_SetString(PyExc_TypeError,
+                        "expected a 'cdata struct-or-union' object");
+        return NULL;
+    }
+    if ((ct->ct_flags & CT_POINTER) == 0 ||
+        (ct->ct_itemdescr != cd->c_type)) {
+        PyErr_SetString(PyExc_TypeError,
+                        "arg 1 must be the type of pointers to arg 2");
+        return NULL;
+    }
+    return new_simple_cdata(cd->c_data, ct);
+}
+
 static PyObject *b_getcname(PyObject *self, PyObject *args)
 {
     CTypeDescrObject *ct;
     {"sizeof", b_sizeof, METH_O},
     {"typeof", b_typeof, METH_O},
     {"offsetof", b_offsetof, METH_VARARGS},
+    {"addressof", b_addressof, METH_VARARGS},
     {"getcname", b_getcname, METH_VARARGS},
     {"string", b_string, METH_VARARGS},
     {"buffer", b_buffer, METH_VARARGS},
     BFunc20 = new_function_type((BStructPtr,), BShort, False)
     f = cast(BFunc20, _testfunc(20))
     x = newp(BStructPtr, {'a1': b'A', 'a2': -4042})
-    # test the exception that allows us to pass a 'struct foo' where the
-    # function really expects a 'struct foo *'.
-    res = f(x[0])
-    assert res == -4042 + ord(b'A')
-    assert res == f(x)
+    # can't pass a 'struct foo'
+    py.test.raises(TypeError, f, x[0])
 
 def test_call_function_21():
     BInt = new_primitive_type("int")
     BDouble = new_primitive_type("double")
     assert int(cast(BBool, cast(BDouble, 0.1))) == 1
     assert int(cast(BBool, cast(BDouble, 0.0))) == 0
+
+def test_addressof():
+    BChar = new_primitive_type("char")
+    BStruct = new_struct_type("foo")
+    BStructPtr = new_pointer_type(BStruct)
+    complete_struct_or_union(BStruct, [('a1', BChar, -1),
+                                       ('a2', BChar, -1),
+                                       ('a3', BChar, -1)])
+    p = newp(BStructPtr)
+    assert repr(p) == "<cdata 'struct foo *' owning 3 bytes>"
+    s = p[0]
+    assert repr(s) == "<cdata 'struct foo' owning 3 bytes>"
+    a = addressof(BStructPtr, s)
+    assert repr(a).startswith("<cdata 'struct foo *' 0x")
+    py.test.raises(TypeError, addressof, BStruct, s)
+    py.test.raises(TypeError, addressof, new_pointer_type(BChar), s)
+    py.test.raises(TypeError, addressof, BStructPtr, a)
+    py.test.raises(TypeError, addressof, new_pointer_type(BStructPtr), a)
+    py.test.raises(TypeError, addressof, BStructPtr, cast(BChar, '?'))
         self._new_types = types.ModuleType('new_types').__dict__
         self._function_caches = []
         self._cdefsources = []
+        self._pointer_type_cache = {}
         if hasattr(backend, 'set_ffi'):
             backend.set_ffi(self)
         #
     errno = property(_get_errno, _set_errno, None,
                      "the value of 'errno' from/to the C calls")
 
+    def addressof(self, cdata):
+        """Return the address of a <cdata 'struct-or-union'>."""
+        ctype = self._backend.typeof(cdata)
+        try:
+            ctypeptr = self._pointer_type_cache[ctype]
+        except KeyError:
+            ctypeptr = self._pointer_type_cache[ctype] = (
+                self._typeof(self.getctype(ctype, '*')))
+        return self._backend.addressof(ctypeptr, cdata)
+
+
 def _make_ffi_library(ffi, libname):
     name = libname
     if name is None:

File cffi/backend_ctypes.py

     def getcname(self, BType, replace_with):
         return BType._get_c_name(replace_with)
 
+    def addressof(self, BTypePtr, cdata):
+        if not isinstance(cdata, CTypesBaseStructOrUnion):
+            raise TypeError("expected a <cdata 'struct-or-union'>")
+        ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata))
+        return BTypePtr._from_ctypes(ptr)
+
 
 class CTypesLibrary(object):
 

File testing/backend_tests.py

             };
         """)
         q = ffi.new("struct foo_s *")
+
+    def test_addressof(self):
+        ffi = FFI(backend=self.Backend())
+        ffi.cdef("struct foo_s { int x, y; };")
+        p = ffi.new("struct foo_s *")
+        a = ffi.addressof(p[0])
+        assert repr(a).startswith("<cdata 'struct foo_s *' 0x")
+        py.test.raises(TypeError, ffi.addressof, p)
+        py.test.raises((AttributeError, TypeError), ffi.addressof, 5)

File testing/test_verify.py

             type = '%s %s' % (sign, basetype)
             assert int(ffi.cast("_Bool", ffi.cast(type, 42))) == 1
             assert int(ffi.cast("_Bool", ffi.cast(type, 0))) == 0
+
+def test_addressof():
+    ffi = FFI()
+    ffi.cdef("""
+        struct point_s { int x, y; };
+        struct foo_s { int z; struct point_s point; };
+        struct point_s sum_coord(struct point_s *);
+    """)
+    lib = ffi.verify("""
+        struct point_s { int x, y; };
+        struct foo_s { int z; struct point_s point; };
+        struct point_s sum_coord(struct point_s *point) {
+            struct point_s r;
+            r.x = point->x + point->y;
+            r.y = point->x - point->y;
+            return r;
+        }
+    """)
+    p = ffi.new("struct foo_s *")
+    p.point.x = 16
+    p.point.y = 9
+    py.test.raises(TypeError, lib.sum_coord, p.point)
+    res = lib.sum_coord(ffi.addressof(p.point))
+    assert res.x == 25
+    assert res.y == 7
+    res2 = lib.sum_coord(ffi.addressof(res))
+    assert res2.x == 32
+    assert res2.y == 18
+    py.test.raises(TypeError, lib.sum_coord, res2)