8000 gh-112433: Add optional _align_ attribute to ctypes.Structure (GH-113… · python/cpython@298bcdc · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 298bcdc

Browse files
authored
gh-112433: Add optional _align_ attribute to ctypes.Structure (GH-113790)
1 parent f42e112 commit 298bcdc

File tree

8 files changed

+328
-1
lines changed

8 files changed

+328
-1
lines changed

Doc/library/ctypes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ compiler does it. It is possible to override this behavior by specifying a
670670
:attr:`~Structure._pack_` class attribute in the subclass definition.
671671
This must be set to a positive integer and specifies the maximum alignment for the fields.
672672
This is what ``#pragma pack(n)`` also does in MSVC.
673+
It is also possible to set a minimum alignment for how the subclass itself is packed in the
674+
same way ``#pragma align(n)`` works in MSVC.
675+
This can be achieved by specifying a ::attr:`~Structure._align_` class attribute
676+
in the subclass definition.
673677

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

25362540

2541+
.. attribute:: _align_
2542+
2543+
An optional small integer that allows overriding the alignment of
2544+
the structure when being packed or unpacked to/from memory.
2545+
Setting this attribute to 0 is the same as not setting it at all.
2546+
25372547
.. attribute:: _anonymous_
25382548

25392549
An optional sequence that lists the names of unnamed (anonymous) fields.

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_global_strings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ struct _Py_global_strings {
231231
STRUCT_FOR_ID(_abc_impl)
232232
STRUCT_FOR_ID(_abstract_)
233233
STRUCT_FOR_ID(_active)
234+
STRUCT_FOR_ID(_align_)
234235
STRUCT_FOR_ID(_annotation)
235236
STRUCT_FOR_ID(_anonymous_)
236237
STRUCT_FOR_ID(_argtypes_)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_unicodeobject_generated.h

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 286 additions & 0 deletions
< F438 /tr>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
from ctypes import (
2+
c_char, c_uint32, c_uint16, c_ubyte, c_byte, alignment, sizeof,
3+
BigEndianStructure, LittleEndianStructure,
4+
BigEndianUnion, LittleEndianUnion,
5+
)
6+
import struct
7+
import unittest
8+
9+
10+
class TestAlignedStructures(unittest.TestCase):
11+
def test_aligned_string(self):
12+
for base, e in (
13+
(LittleEndianStructure, "<"),
14+
(BigEndianStructure, ">"),
15+
):
16+
data = bytearray(struct.pack(f"{e}i12x16s", 7, b"hello world!"))
17+
class Aligned(base):
18+
_align_ = 16
19+
_fields_ = [
20+
('value', c_char * 12)
21+
]
22+
23+
class Main(base):
24+
_fields_ = [
25+
('first', c_uint32),
26+
('string', Aligned),
27+
]
28+
29+
main = Main.from_buffer(data)
30+
self.assertEqual(main.first, 7)
31+
self.assertEqual(main.string.value, b'hello world!')
32+
self.assertEqual(bytes(main.string), b'hello world!\0\0\0\0')
33+
self.assertEqual(Main.string.offset, 16)
34+
self.assertEqual(Main.string.size, 16)
35+
self.assertEqual(alignment(main.string), 16)
36+
self.assertEqual(alignment(main), 16)
37+
38+
def test_aligned_structures(self):
39+
for base, data in (
40+
(LittleEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")),
41+
(BigEndianStructure, bytearray(b"\1\0\0\0\1\0\0\0\7\0\0\0")),
42+
):
43+
class SomeBools(base):
44+
_align_ = 4
45+
_fields_ = [
46+
("bool1", c_ubyte),
47+
("bool2", c_ubyte),
48+
]
49+
class Main(base):
50+
_fields_ = [
51+
("x", c_ubyte),
52+
("y", SomeBools),
53+
("z", c_ubyte),
54+
]
55+
56+
main = Main.from_buffer(data)
57+
self.assertEqual(alignment(SomeBools), 4)
58+
self.assertEqual(alignment(main), 4)
59+
self.assertEqual(alignment(main.y), 4)
60+
self.assertEqual(Main.x.size, 1)
61+
self.assertEqual(Main.y.offset, 4)
62+
self.assertEqual(Main.y.size, 4)
63+
self.assertEqual(main.y.bool1, True)
64+
self.assertEqual(main.y.bool2, False)
65+
self.assertEqual(Main.z.offset, 8)
66+
self.assertEqual(main.z, 7)
67+
68+
def test_oversized_structure(self):
69+
data = bytearray(b"\0" * 8)
70+
for base in (LittleEndianStructure, BigEndianStructure):
71+
class SomeBoolsTooBig(base):
72+
_align_ = 8
73+
_fields_ = [
74+
("bool1", c_ubyte),
75+
("bool2", c_ubyte),
76+
("bool3", c_ubyte),
77+
]
78+
class Main(base):
79+
_fields_ = [
80+
("y", SomeBoolsTooBig),
81+
("z", c_uint32),
82+
]
83+
with self.assertRaises(ValueError) as ctx:
84+
Main.from_buffer(data)
85+
self.assertEqual(
86+
ctx.exception.args[0],
87+
'Buffer size too small (4 instead of at least 8 bytes)'
88+
)
89+
90+
def test_aligned_subclasses(self):
91+
for base, e in (
92+
(LittleEndianStructure, "<"),
93+
(BigEndianStructure, ">"),
94+
):
95+
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
96+
class UnalignedSub(base):
97+
x: c_uint32
98+
_fields_ = [
99+
("x", c_uint32),
100+
]
101+
102+
class AlignedStruct(UnalignedSub):
103+
_align_ = 8
104+
_fields_ = [
105+
("y", c_uint32),
106+
]
107+
108+
class Main(base):
109+
_fields_ = [
110+
("a", c_uint32),
111+
("b", AlignedStruct)
112+
]
113+
114+
main = Main.from_buffer(data)
115+
self.assertEqual(alignment(main.b), 8)
116+
self.assertEqual(alignment(main), 8)
117+
self.assertEqual(sizeof(main.b), 8)
118+
self.assertEqual(sizeof(main), 16)
119+
self.assertEqual(main.a, 1)
120+
self.assertEqual(main.b.x, 3)
121+
self.assertEqual(main.b.y, 4)
122+
self.assertEqual(Main.b.offset, 8)
123+
self.assertEqual(Main.b.size, 8)
124+
125+
def test_aligned_union(self):
126+
for sbase, ubase, e in (
127+
(LittleEndianStructure, LittleEndianUnion, "<"),
128+
(BigEndianStructure, BigEndianUnion, ">"),
129+
):
130+
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
131+
class AlignedUnion(ubase):
132+
_align_ = 8
133+
_fields_ = [
134+
("a", c_uint32),
135+
("b", c_ubyte * 7),
136+
]
137+
138+
class Main(sbase):
139+
_fields_ = [
140+
("first", c_uint32),
141+
("union", AlignedUnion),
142+
]
143+
144+
main = Main.from_buffer(data)
145+
self.assertEqual(main.first, 1)
146+
self.assertEqual(main.union.a, 3)
147+
self.assertEqual(bytes(main.union.b), data[8:-1])
148+
self.assertEqual(Main.union.offset, 8)
149+
self.assertEqual(Main.union.size, 8)
150+
self.assertEqual(alignment(main.union), 8)
151+
self.assertEqual(alignment(main), 8)
152+
153+
def test_aligned_struct_in_union(self):
154+
for sbase, ubase, e in (
155+
(LittleEndianStructure, LittleEndianUnion, "<"),
156+
(BigEndianStructure, BigEndianUnion, ">"),
157+
):
158+
data = bytearray(struct.pack(f"{e}4i", 1, 2, 3, 4))
159+
class Sub(sbase):
160+
_align_ = 8
161+
_fields_ = [
162+
("x", c_uint32),
163+
("y", c_uint32),
164+
]
165+
166+
class MainUnion(ubase):
167+
_fields_ = [
168+
("a", c_uint32),
169+
("b", Sub),
170+
]
171+
172+
class Main(sbase):
173+
_fields_ = [
174+
("first", c_uint32),
175+
("union", MainUnion),
176+
]
177+
178+
main = Main.from_buffer(data)
179+
self.assertEqual(Main.first.size, 4)
180+
self.assertEqual(alignment(main.union), 8)
181+
self.assertEqual(alignment(main), 8)
182+
self.assertEqual(Main.union.offset, 8)
183+
self.assertEqual(Main.union.size, 8)
184+
self.assertEqual(main.first, 1)
185+
self.assertEqual(main.union.a, 3)
186+
self.assertEqual(main.union.b.x, 3)
187+
self.assertEqual(main.union.b.y, 4)
188+
189+
def test_smaller_aligned_subclassed_union(self):
190+
for sbase, ubase, e in (
191+
(LittleEndianStructure, LittleEndianUnion, "<"),
192+
(BigEndianStructure, BigEndianUnion, ">"),
193+
):
194+
data = bytearray(struct.pack(f"{e}H2xI", 1, 0xD60102D7))
195+
class SubUnion(ubase):
196+
_align_ = 2
197+
_fields_ = [
198+
("unsigned", c_ubyte),
199+
("signed", c_byte),
200+
]
201+
202+
class MainUnion(SubUnion):
203+
_fields_ = [
204+
("num", c_uint32)
205+
]
206+
207+
class Main(sbase):
208+
_fields_ = [
209+
("first", c_uint16),
210+
("union", MainUnion),
211+
]
212+
213+
main = Main.from_buffer(data)
214+
self.assertEqual(main.union.num, 0xD60102D7)
215+
self.assertEqual(main.union.unsigned, data[4])
216+
self.assertEqual(main.union.signed, data[4] - 256)
217+
self.assertEqual(alignment(main), 4)
218+
self.assertEqual(alignment(main.union), 4)
219+
self.assertEqual(Main.union.offset, 4)
220+
self.assertEqual(Main.union.size, 4)
221+
self.assertEqual(Main.first.size, 2)
222+
223+
def test_larger_aligned_subclassed_union(self):
224+
for ubase, e in (
225+
(LittleEndianUnion, "<"),
226+
(BigEndianUnion, ">"),
227+
):
228+
data = bytearray(struct.pack(f"{e}I4x", 0xD60102D6))
229+
class SubUnion(ubase):
230+
_align_ = 8
231+
_fields_ = [
232+
("unsigned", c_ubyte),
233+
("signed", c_byte),
234+
]
235+
236+
class Main(SubUnion):
237+
_fields_ = [
238+
("num", c_uint32)
239+
]
240+
241+
main = Main.from_buffer(data)
242+
self.assertEqual(alignment(main), 8)
243+
self.assertEqual(sizeof(main), 8)
244+
self.assertEqual(main.num, 0xD60102D6)
245+
self.assertEqual(main.unsigned, 0xD6)
246+
self.assertEqual(main.signed, -42)
247+
248+
def test_aligned_packed_structures(self):
249+
for sbase, e in (
250+
(LittleEndianStructure, "<"),
251+
(BigEndianStructure, ">"),
252+
):
253+
data = bytearray(struct.pack(f"{e}B2H4xB", 1, 2, 3, 4))
254+
255+
class Inner(sbase):
256+
_align_ = 8
257+
_fields_ = [
258+
("x", c_uint16),
259+
("y", c_uint16),
260+
]
261+
262+
class Main(sbase):
263+
_pack_ = 1
264+
_fields_ = [
265+
("a", c_ubyte),
266+
("b", Inner),
267+
("c", c_ubyte),
268+
]
269+
270+
main = Main.from_buffer(data)
271+
self.assertEqual(sizeof(main), 10)
272+
self.assertEqual(Main.b.offset, 1)
273+
# Alignment == 8 because _pack_ wins out.
274+
self.assertEqual(alignment(main.b), 8)
275+
# Size is still 8 though since inside this Structure, it will have
276+
# effect.
277+
self.assertEqual(sizeof(main.b), 8)
278+
self.assertEqual(Main.c.offset, 9)
279+
self.assertEqual(main.a, 1)
280+
self.assertEqual(main.b.x, 2)
281+
self.assertEqual(main.b.y, 3)
282+
self.assertEqual(main.c, 4)
283+
284+
285+
if __name__ == '__main__':
286+
unittest.main()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ability to force alignment of :mod:`ctypes.Structure` by way of the new ``_align_`` attribute on the class.

0 commit comments

Comments
 (0)
0