Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,10 @@ compiler does it. It is possible to override this behavior by specifying a
:attr:`~Structure._pack_` class attribute in the subclass definition.
This must be set to a positive integer and specifies the maximum alignment for the fields.
This is what ``#pragma pack(n)`` also does in MSVC.
It is also possible to set a minimum alignment for how the subclass itself is packed in the
same way ``#pragma align(n)`` works in MSVC.
This can be achieved by specifying a ::attr:`~Structure._align_` class attribute
in the subclass definition.

:mod:`ctypes` uses the native byte order for Structures and Unions. To build
structures with non-native byte order, you can use one of the
Expand Down Expand Up @@ -2534,6 +2538,12 @@ fields, or any other data types containing pointer type fields.
Setting this attribute to 0 is the same as not setting it at all.


.. attribute:: _align_

An optional small interger that allows overriding the alignment of
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated
the structure when being packed or unpacked to/from memory.
Setting this attribute to 0 is the same as not setting it at all.

.. attribute:: _anonymous_

An optional sequence that lists the names of unnamed (anonymous) fields.
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_global_objects_fini_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Include/internal/pycore_global_strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(_abc_impl)
STRUCT_FOR_ID(_abstract_)
STRUCT_FOR_ID(_active)
STRUCT_FOR_ID(_align_)
STRUCT_FOR_ID(_annotation)
STRUCT_FOR_ID(_anonymous_)
STRUCT_FOR_ID(_argtypes_)
Expand Down
1 change: 1 addition & 0 deletions Include/internal/pycore_runtime_init_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Include/internal/pycore_unicodeobject_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

165 changes: 165 additions & 0 deletions Lib/test/test_ctypes/test_aligned_structures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
from ctypes import Structure, c_char, c_uint32, c_ubyte, alignment, sizeof, Union
import inspect
import unittest


class TestAlignedStructures(unittest.TestCase):
def test_aligned_string(self):
data = bytearray(
b'\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00'
)
Comment thread
monkeyman192 marked this conversation as resolved.
Outdated

class Aligned(Structure):
_align_ = 16
_fields_ = [
('value', c_char * 16),
]

class Main(Structure):
_fields_ = [
('first', c_uint32),
('string', Aligned),
]

main = Main.from_buffer(data)
self.assertEqual(main.first, 7)
self.assertEqual(main.string.value, b'hello world!')
self.assertEqual(
bytes(main.string.__buffer__(inspect.BufferFlags.SIMPLE)),
b'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x21\x00\x00\x00\x00'
)

def test_aligned_structures(self):
Comment thread
serhiy-storchaka marked this conversation as resolved.
data = bytearray(
b'\x01\x00\x01\x00\x07\x00\x00\x00'
)

class SomeBools(Structure):
_align_ = 4
_fields_ = [
("bool1", c_ubyte),
("bool2", c_ubyte),
("bool3", c_ubyte),
]
class Main(Structure):
_fields_ = [
("x", SomeBools),
("y", c_uint32),
]

class SomeBoolsTooBig(Structure):
_align_ = 8
_fields_ = [
("bool1", c_ubyte),
("bool2", c_ubyte),
("bool3", c_ubyte),
]
class MainTooBig(Structure):
_fields_ = [
("x", SomeBoolsTooBig),
("y", c_uint32),
]
main = Main.from_buffer(data)
self.assertEqual(main.y, 7)
self.assertEqual(alignment(SomeBools), 4)
self.assertEqual(main.x.bool1, True)
self.assertEqual(main.x.bool2, False)
self.assertEqual(main.x.bool3, True)

with self.assertRaises(ValueError) as ctx:
MainTooBig.from_buffer(data)
self.assertEqual(
ctx.exception.args[0],
'Buffer size too small (4 instead of at least 8 bytes)'
)

def test_aligned_subclasses(self):
data = bytearray(
b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
)

class UnalignedSub(Structure):
x: c_uint32
_fields_ = [
("x", c_uint32),
]

class AlignedStruct(UnalignedSub):
_align_ = 8
y: c_uint32
_fields_ = [
("y", c_uint32),
]

class Main(Structure):
_fields_ = [
("a", c_uint32),
("b", AlignedStruct)
]

main = Main.from_buffer(data)
self.assertEqual(alignment(main.b), 8)
self.assertEqual(alignment(main), 8)
self.assertEqual(sizeof(main.b), 8)
self.assertEqual(sizeof(main), 16)
self.assertEqual(main.a, 1)
self.assertEqual(main.b.x, 3)
self.assertEqual(main.b.y, 4)

def test_aligned_union(self):
data = bytearray(
b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
)

class AlignedUnion(Union):
_align_ = 8
_fields_ = [
("a", c_uint32),
("b", c_ubyte * 8),
]

class Main(Structure):
_fields_ = [
("first", c_uint32),
("union", AlignedUnion),
]

main = Main.from_buffer(data)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(bytes(main.union.b), b"\x03\x00\x00\x00\x04\x00\x00\x00")

def test_aligned_struct_in_union(self):
data = bytearray(
b"\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x04\x00\x00\x00"
)

class Sub(Structure):
_align_ = 8
_fields_ = [
("x", c_uint32),
("y", c_uint32),
]

class MainUnion(Union):
_fields_ = [
("a", c_uint32),
("b", Sub),
]

class Main(Structure):
_fields_ = [
("first", c_uint32),
("union", MainUnion),
]

main = Main.from_buffer(data)
self.assertEqual(main.first, 1)
self.assertEqual(main.union.a, 3)
self.assertEqual(main.union.b.x, 3)
self.assertEqual(main.union.b.y, 4)


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add ability to force alignment of :mod:`ctypes.Structure` by way of the new ``_align_`` attribute on the class.
27 changes: 25 additions & 2 deletions Modules/_ctypes/stgdict.c
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
int bitofs;
PyObject *tmp;
int pack;
int forced_alignment = 1;
Py_ssize_t ffi_ofs;
int big_endian;
int arrays_seen = 0;
Expand Down Expand Up @@ -419,6 +420,28 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
pack = 0;
}

if (PyObject_GetOptionalAttr(type, &_Py_ID(_align_), &tmp) < 0) {
return -1;
}
if (tmp) {
forced_alignment = PyLong_AsInt(tmp);
Py_DECREF(tmp);
if (forced_alignment < 0) {
if (!PyErr_Occurred() ||
PyErr_ExceptionMatches(PyExc_TypeError) ||
PyErr_ExceptionMatches(PyExc_OverflowError))
{
PyErr_SetString(PyExc_ValueError,
"_align_ must be a non-negative integer");
}
return -1;
}
}
else {
/* Setting `_align_ = 0` amounts to using the default alignment */
forced_alignment = 1;
}

len = PySequence_Size(fields);
if (len == -1) {
if (PyErr_ExceptionMatches(PyExc_TypeError)) {
Expand Down Expand Up @@ -463,7 +486,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
size = offset = basedict->size;
align = basedict->align;
union_size = 0;
total_align = align ? align : 1;
total_align = max(align ? align : 1, forced_alignment);
Comment thread
monkeyman192 marked this conversation as resolved.
Outdated
stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT;
stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, basedict->length + len + 1);
if (stgdict->ffi_type_pointer.elements == NULL) {
Expand All @@ -483,7 +506,7 @@ PyCStructUnionType_update_stgdict(PyObject *type, PyObject *fields, int isStruct
size = 0;
align = 0;
union_size = 0;
total_align = 1;
total_align = forced_alignment;
stgdict->ffi_type_pointer.type = FFI_TYPE_STRUCT;
stgdict->ffi_type_pointer.elements = PyMem_New(ffi_type *, len + 1);
if (stgdict->ffi_type_pointer.elements == NULL) {
Expand Down