summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArmin Rigo <arigo@tunes.org>2014-01-14 09:06:06 +0100
committerArmin Rigo <arigo@tunes.org>2014-01-14 09:06:06 +0100
commitcaedfd9210778c664494c16ec14540bfba3a9412 (patch)
treef628b55eb5d0aa29372227a9afc036640fe96149
parent598008bfc3a39a69ca00c32b2405524c4740ebea (diff)
downloadcffi-caedfd9210778c664494c16ec14540bfba3a9412.tar.gz
Issue 131: support ffi.cdef("...", packed=True)
-rw-r--r--c/_cffi_backend.c5
-rw-r--r--c/test_c.py28
-rw-r--r--cffi/api.py6
-rw-r--r--cffi/backend_ctypes.py4
-rw-r--r--cffi/cparser.py7
-rw-r--r--cffi/model.py7
-rw-r--r--doc/source/index.rst8
-rw-r--r--testing/backend_tests.py18
8 files changed, 76 insertions, 7 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
index 1fa38bc..85fe49d 100644
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -3586,6 +3586,7 @@ _add_field(PyObject *interned_fields, PyObject *fname, CTypeDescrObject *ftype,
#define SF_MSVC_BITFIELDS 1
#define SF_GCC_ARM_BITFIELDS 2
#define SF_GCC_BIG_ENDIAN 4
+#define SF_PACKED 8
static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args)
{
@@ -3671,8 +3672,8 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args)
boffset = 0; /* reset each field at offset 0 */
/* update the total alignment requirement, but skip it if the
- field is an anonymous bitfield */
- falign = get_alignment(ftype);
+ field is an anonymous bitfield or if SF_PACKED */
+ falign = (sflags & SF_PACKED) ? 1 : get_alignment(ftype);
if (falign < 0)
goto error;
diff --git a/c/test_c.py b/c/test_c.py
index 6249260..005a089 100644
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3148,6 +3148,34 @@ def test_sizeof_sliced_array():
p = newp(BArray, None)
assert sizeof(p[2:9]) == 7 * sizeof(BInt)
+def test_packed():
+ BLong = new_primitive_type("long")
+ BChar = new_primitive_type("char")
+ BShort = new_primitive_type("short")
+ BStruct = new_struct_type("struct foo")
+ complete_struct_or_union(BStruct, [('a1', BLong, -1),
+ ('a2', BChar, -1),
+ ('a3', BShort, -1)],
+ None, -1, -1, 8) # SF_PACKED==8
+ d = BStruct.fields
+ assert len(d) == 3
+ assert d[0][0] == 'a1'
+ assert d[0][1].type is BLong
+ assert d[0][1].offset == 0
+ assert d[0][1].bitshift == -1
+ assert d[0][1].bitsize == -1
+ assert d[1][0] == 'a2'
+ assert d[1][1].type is BChar
+ assert d[1][1].offset == sizeof(BLong)
+ assert d[1][1].bitshift == -1
+ assert d[1][1].bitsize == -1
+ assert d[2][0] == 'a3'
+ assert d[2][1].type is BShort
+ assert d[2][1].offset == sizeof(BLong) + sizeof(BChar)
+ assert d[2][1].bitshift == -1
+ assert d[2][1].bitsize == -1
+ assert sizeof(BStruct) == sizeof(BLong) + sizeof(BChar) + sizeof(BShort)
+ assert alignof(BStruct) == 1
def test_version():
# this test is here mostly for PyPy
diff --git a/cffi/api.py b/cffi/api.py
index b899e5b..de881e8 100644
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -88,18 +88,20 @@ class FFI(object):
self.NULL = self.cast(self.BVoidP, 0)
self.CData, self.CType = backend._get_types()
- def cdef(self, csource, override=False):
+ def cdef(self, csource, override=False, packed=False):
"""Parse the given C source. This registers all declared functions,
types, and global variables. The functions and global variables can
then be accessed via either 'ffi.dlopen()' or 'ffi.verify()'.
The types can be used in 'ffi.new()' and other functions.
+ If 'packed' is specified as True, all structs declared inside this
+ cdef are packed, i.e. laid out without any field alignment at all.
"""
if not isinstance(csource, str): # unicode, on Python 2
if not isinstance(csource, basestring):
raise TypeError("cdef() argument must be a string")
csource = csource.encode('ascii')
with self._lock:
- self._parser.parse(csource, override=override)
+ self._parser.parse(csource, override=override, packed=packed)
self._cdefsources.append(csource)
if override:
for cache in self._function_caches:
diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py
index e7cd8a2..2b2b481 100644
--- a/cffi/backend_ctypes.py
+++ b/cffi/backend_ctypes.py
@@ -720,7 +720,7 @@ class CTypesBackend(object):
return self._new_struct_or_union('union', name, ctypes.Union)
def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp,
- totalsize=-1, totalalignment=-1):
+ totalsize=-1, totalalignment=-1, sflags=0):
if totalsize >= 0 or totalalignment >= 0:
raise NotImplementedError("the ctypes backend of CFFI does not support "
"structures completed by verify(); please "
@@ -739,6 +739,8 @@ class CTypesBackend(object):
else:
cfields.append((fname, BField._ctype, bitsize))
bfield_types[fname] = Ellipsis
+ if sflags & 8:
+ struct_or_union._pack_ = 1
struct_or_union._fields_ = cfields
CTypesStructOrUnion._bfield_types = bfield_types
#
diff --git a/cffi/cparser.py b/cffi/cparser.py
index 73ab903..99998ac 100644
--- a/cffi/cparser.py
+++ b/cffi/cparser.py
@@ -98,6 +98,7 @@ class Parser(object):
self._anonymous_counter = 0
self._structnode2type = weakref.WeakKeyDictionary()
self._override = False
+ self._packed = False
def _parse(self, csource):
csource, macros = _preprocess(csource)
@@ -147,13 +148,16 @@ class Parser(object):
msg = 'parse error\n%s' % (msg,)
raise api.CDefError(msg)
- def parse(self, csource, override=False):
+ def parse(self, csource, override=False, packed=False):
prev_override = self._override
+ prev_packed = self._packed
try:
self._override = override
+ self._packed = packed
self._internal_parse(csource)
finally:
self._override = prev_override
+ self._packed = prev_packed
def _internal_parse(self, csource):
ast, macros = self._parse(csource)
@@ -476,6 +480,7 @@ class Parser(object):
if isinstance(tp, model.StructType) and tp.partial:
raise NotImplementedError("%s: using both bitfields and '...;'"
% (tp,))
+ tp.packed = self._packed
return tp
def _make_partial(self, tp, nested):
diff --git a/cffi/model.py b/cffi/model.py
index bf23c22..d348be9 100644
--- a/cffi/model.py
+++ b/cffi/model.py
@@ -255,6 +255,7 @@ class StructOrUnion(StructOrUnionOrEnum):
fixedlayout = None
completed = False
partial = False
+ packed = False
def __init__(self, name, fldnames, fldtypes, fldbitsize):
self.name = name
@@ -311,7 +312,11 @@ class StructOrUnion(StructOrUnionOrEnum):
fldtypes = [tp.get_cached_btype(ffi, finishlist)
for tp in self.fldtypes]
lst = list(zip(self.fldnames, fldtypes, self.fldbitsize))
- ffi._backend.complete_struct_or_union(BType, lst, self)
+ sflags = 0
+ if self.packed:
+ sflags = 8 # SF_PACKED
+ ffi._backend.complete_struct_or_union(BType, lst, self,
+ -1, -1, sflags)
#
else:
fldtypes = []
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 3b9d87b..fe3a8a7 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -851,6 +851,14 @@ like ``ffi.new("int[%d]" % x)``. Indeed, this is not recommended:
``ffi`` normally caches the string ``"int[]"`` to not need to re-parse
it all the time.
+.. versionadded:: 0.9
+ The ``ffi.cdef()`` call takes an optional argument ``packed``: if
+ True, then all structs declared within this cdef are "packed". This
+ has a meaning similar to ``__attribute__((packed))`` in GCC. It
+ specifies that all structure fields should have an alignment of one
+ byte. (Note that the packed attribute has no effect on bit fields so
+ far, which mean that they may be packed differently than on GCC.)
+
Python 3 support
----------------
diff --git a/testing/backend_tests.py b/testing/backend_tests.py
index ab25869..ff5dd50 100644
--- a/testing/backend_tests.py
+++ b/testing/backend_tests.py
@@ -1549,3 +1549,21 @@ class BackendTests:
ffi2.include(ffi1)
p = ffi2.new("foo_p", [142])
assert p.x == 142
+
+ def test_struct_packed(self):
+ ffi = FFI(backend=self.Backend())
+ ffi.cdef("struct nonpacked { char a; int b; };")
+ ffi.cdef("struct is_packed { char a; int b; };", packed=True)
+ assert ffi.sizeof("struct nonpacked") == 8
+ assert ffi.sizeof("struct is_packed") == 5
+ assert ffi.alignof("struct nonpacked") == 4
+ assert ffi.alignof("struct is_packed") == 1
+ s = ffi.new("struct is_packed[2]")
+ s[0].b = 42623381
+ s[0].a = 'X'
+ s[1].b = -4892220
+ s[1].a = 'Y'
+ assert s[0].b == 42623381
+ assert s[0].a == 'X'
+ assert s[1].b == -4892220
+ assert s[1].a == 'Y'