Commits

Armin Rigo  committed fcbef63

ffi.addressof(struct, field).

  • Participants
  • Parent commits 7aaba88

Comments (0)

Files changed (6)

File c/_cffi_backend.c

     return PyInt_FromSsize_t(cf->cf_offset);
 }
 
-static PyObject *b_addressof(PyObject *self, PyObject *args)
+static PyObject *b_typeoffsetof(PyObject *self, PyObject *args)
+{
+    PyObject *res, *fieldname;
+    CTypeDescrObject *ct;
+    CFieldObject *cf;
+    Py_ssize_t offset;
+
+    if (!PyArg_ParseTuple(args, "O!O:typeof",
+                          &CTypeDescr_Type, &ct, &fieldname))
+        return NULL;
+
+    if (fieldname == Py_None) {
+        if (!(ct->ct_flags & (CT_STRUCT|CT_UNION)))
+            goto typeerror;
+        res = (PyObject *)ct;
+        offset = 0;
+    }
+    else {
+        if (ct->ct_flags & CT_POINTER)
+            ct = ct->ct_itemdescr;
+        if (!(ct->ct_flags & (CT_STRUCT|CT_UNION)))
+            goto typeerror;
+        if (ct->ct_flags & CT_IS_OPAQUE)
+            cf = NULL;
+        else
+            cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, fieldname);
+        if (cf == NULL) {
+            PyErr_SetObject(PyExc_KeyError, fieldname);
+            return NULL;
+        }
+        if (cf->cf_bitshift >= 0) {
+            PyErr_SetString(PyExc_TypeError, "not supported for bitfields");
+            return NULL;
+        }
+        res = (PyObject *)cf->cf_type;
+        offset = cf->cf_offset;
+    }
+    return Py_BuildValue("(On)", res, offset);
+
+ typeerror:
+    PyErr_SetString(PyExc_TypeError, "expected a struct or union ctype");
+    return NULL;
+}
+
+static PyObject *b_rawaddressof(PyObject *self, PyObject *args)
 {
     CTypeDescrObject *ct;
     CDataObject *cd;
-
-    if (!PyArg_ParseTuple(args, "O!O!:addressof",
+    Py_ssize_t offset = 0;
+
+    if (!PyArg_ParseTuple(args, "O!O!|n:rawaddressof",
                           &CTypeDescr_Type, &ct,
-                          &CData_Type, &cd))
+                          &CData_Type, &cd,
+                          &offset))
         return NULL;
 
-    if ((cd->c_type->ct_flags & (CT_STRUCT|CT_UNION)) == 0) {
+    if ((cd->c_type->ct_flags & (CT_STRUCT|CT_UNION|CT_IS_PTR_TO_OWNED)) == 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)) {
+    if ((ct->ct_flags & CT_POINTER) == 0) {
         PyErr_SetString(PyExc_TypeError,
-                        "arg 1 must be the type of pointers to arg 2");
+                        "expected a pointer ctype");
         return NULL;
     }
-    return new_simple_cdata(cd->c_data, ct);
+    return new_simple_cdata(cd->c_data + offset, ct);
 }
 
 static PyObject *b_getcname(PyObject *self, PyObject *args)
     {"sizeof", b_sizeof, METH_O},
     {"typeof", b_typeof, METH_O},
     {"offsetof", b_offsetof, METH_VARARGS},
-    {"addressof", b_addressof, METH_VARARGS},
+    {"typeoffsetof", b_typeoffsetof, METH_VARARGS},
+    {"rawaddressof", b_rawaddressof, METH_VARARGS},
     {"getcname", b_getcname, METH_VARARGS},
     {"string", b_string, METH_VARARGS},
     {"buffer", b_buffer, METH_VARARGS},
     assert int(cast(BBool, cast(BDouble, 0.1))) == 1
     assert int(cast(BBool, cast(BDouble, 0.0))) == 0
 
-def test_addressof():
+def test_typeoffsetof():
     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)])
+    py.test.raises(TypeError, typeoffsetof, BStructPtr, None)
+    assert typeoffsetof(BStruct, None) == (BStruct, 0)
+    assert typeoffsetof(BStructPtr, 'a1') == (BChar, 0)
+    assert typeoffsetof(BStruct, 'a1') == (BChar, 0)
+    assert typeoffsetof(BStructPtr, 'a2') == (BChar, 1)
+    assert typeoffsetof(BStruct, 'a3') == (BChar, 2)
+    py.test.raises(KeyError, typeoffsetof, BStructPtr, 'a4')
+    py.test.raises(KeyError, typeoffsetof, BStruct, 'a5')
+
+def test_typeoffsetof_no_bitfield():
+    BInt = new_primitive_type("int")
+    BStruct = new_struct_type("foo")
+    complete_struct_or_union(BStruct, [('a1', BInt, 4)])
+    py.test.raises(TypeError, typeoffsetof, BStruct, 'a1')
+
+def test_rawaddressof():
+    BChar = new_primitive_type("char")
+    BCharP = new_pointer_type(BChar)
+    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)
+    a = rawaddressof(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, '?'))
+    py.test.raises(TypeError, rawaddressof, BStruct, s)
+    b = rawaddressof(BCharP, s)
+    assert b == cast(BCharP, p)
+    c = rawaddressof(BStructPtr, a)
+    assert c == a
+    py.test.raises(TypeError, rawaddressof, BStructPtr, cast(BChar, '?'))
+    #
+    d = rawaddressof(BCharP, s, 1)
+    assert d == cast(BCharP, p) + 1
     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'>."""
+    def addressof(self, cdata, field=None):
+        """Return the address of a <cdata 'struct-or-union'>.
+        If 'field' is specified, return the address of this field.
+        """
         ctype = self._backend.typeof(cdata)
+        ctype, offset = self._backend.typeoffsetof(ctype, field)
         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)
+        return self._backend.rawaddressof(ctypeptr, cdata, offset)
 
 
 def _make_ffi_library(ffi, libname):

File cffi/backend_ctypes.py

         btypes = [BField for (fname, BField, bitsize) in fields]
         bitfields = [bitsize for (fname, BField, bitsize) in fields]
         #
+        bfield_types = {}
         cfields = []
         for (fname, BField, bitsize) in fields:
             if bitsize < 0:
                 cfields.append((fname, BField._ctype))
+                bfield_types[fname] = BField
             else:
                 cfields.append((fname, BField._ctype, bitsize))
+                bfield_types[fname] = Ellipsis
         struct_or_union._fields_ = cfields
+        CTypesStructOrUnion._bfield_types = bfield_types
         #
         @staticmethod
         def _create_ctype_obj(init):
     def getcname(self, BType, replace_with):
         return BType._get_c_name(replace_with)
 
-    def addressof(self, BTypePtr, cdata):
-        if not isinstance(cdata, CTypesBaseStructOrUnion):
+    def typeoffsetof(self, BType, fieldname):
+        if fieldname is not None and issubclass(BType, CTypesGenericPtr):
+            BType = BType._BItem
+        if not issubclass(BType, CTypesBaseStructOrUnion):
+            raise TypeError("expected a struct or union ctype")
+        if fieldname is None:
+            return (BType, 0)
+        else:
+            BField = BType._bfield_types[fieldname]
+            if BField is Ellipsis:
+                raise TypeError("not supported for bitfields")
+            return (BField, BType._offsetof(fieldname))
+
+    def rawaddressof(self, BTypePtr, cdata, offset):
+        if isinstance(cdata, CTypesBaseStructOrUnion):
+            ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata))
+        elif isinstance(cdata, CTypesGenericPtr):
+            ptr = type(cdata)._to_ctypes(cdata)
+        else:
             raise TypeError("expected a <cdata 'struct-or-union'>")
-        ptr = ctypes.pointer(type(cdata)._to_ctypes(cdata))
+        if offset != 0:
+            ptr = ctypes.cast(
+                ctypes.c_void_p(
+                    ctypes.cast(ptr, ctypes.c_void_p).value + offset),
+                type(ptr))
         return BTypePtr._from_ctypes(ptr)
 
 

File doc/source/index.rst

 
 .. "versionadded:: 0.3" --- inlined in the previous paragraph
 
-``ffi.addressof(cdata)``: from a cdata whose type is ``struct foo_s``,
-return its "address", as a cdata whose type is ``struct foo_s *``.  Also
-works on unions, but not on any other type.  (It would be difficult
-because only structs and unions are internally stored as an indirect
-pointer to the data.)  The returned pointer is only valid as long as
-the original object is.  *New in version 0.4.*
+``ffi.addressof(cdata, field=None)``: from a cdata whose type is
+``struct foo_s``, return its "address", as a cdata whose type is
+``struct foo_s *``.  Also works on unions, but not on any other type.
+(It would be difficult because only structs and unions are internally
+stored as an indirect pointer to the data.)  If ``field`` is given,
+returns the address of that field in the structure.  The returned
+pointer is only valid as long as the original object is.  *New in
+version 0.4.*
 
 .. "versionadded:: 0.4" --- inlined in the previous paragraph
 

File testing/backend_tests.py

         assert repr(a).startswith("<cdata 'struct foo_s *' 0x")
         py.test.raises(TypeError, ffi.addressof, p)
         py.test.raises((AttributeError, TypeError), ffi.addressof, 5)
+
+    def test_addressof_field(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], 'y')
+        assert repr(a).startswith("<cdata 'int *' 0x")
+        assert int(ffi.cast("uintptr_t", a)) == (
+            int(ffi.cast("uintptr_t", p)) + ffi.sizeof("int"))
+        assert a == ffi.addressof(p, 'y')
+        assert a != ffi.addressof(p, 'x')