bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes (GH-25480)

* bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes
* GH-25480: remove trailing whitespace in ctypes doc
* GH-25480: add news entry blurb
* GH-25480: corrected formatting error in news blurb
* GH-25480: simplified, corrected formatting in news blurb
* GH-25480: remove trailing whitespace in news blurb
* GH-25480: fixed class markup in news blurb
* GH-25480: fixed unsupported type tests and naming per review comments
* GH-25480: fixed whitepace errors
* condensed base class selection for unsupported byte order tests
* added versionadded tags for new EndianUnion classes
This commit is contained in:
Dave Goncalves 2022-03-29 14:26:27 -07:00 committed by GitHub
parent 654bd2152d
commit dc2d8404a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 117 additions and 37 deletions

View File

@ -2390,6 +2390,18 @@ Structured data types
Abstract base class for unions in native byte order.
.. class:: BigEndianUnion(*args, **kw)
Abstract base class for unions in *big endian* byte order.
.. versionadded:: 3.11
.. class:: LittleEndianUnion(*args, **kw)
Abstract base class for unions in *little endian* byte order.
.. versionadded:: 3.11
.. class:: BigEndianStructure(*args, **kw)
Abstract base class for structures in *big endian* byte order.
@ -2399,8 +2411,8 @@ Structured data types
Abstract base class for structures in *little endian* byte order.
Structures with non-native byte order cannot contain pointer type fields, or any
other data types containing pointer type fields.
Structures and unions with non-native byte order cannot contain pointer type
fields, or any other data types containing pointer type fields.
.. class:: Structure(*args, **kw)

View File

@ -548,6 +548,7 @@ if _os.name == "nt": # COM stuff
return ccom.DllCanUnloadNow()
from ctypes._endian import BigEndianStructure, LittleEndianStructure
from ctypes._endian import BigEndianUnion, LittleEndianUnion
# Fill in specifically-sized types
c_int8 = c_byte

View File

@ -20,7 +20,7 @@ def _other_endian(typ):
return typ
raise TypeError("This type does not support other endian: %s" % typ)
class _swapped_meta(type(Structure)):
class _swapped_meta:
def __setattr__(self, attrname, value):
if attrname == "_fields_":
fields = []
@ -31,6 +31,8 @@ class _swapped_meta(type(Structure)):
fields.append((name, _other_endian(typ)) + rest)
value = fields
super().__setattr__(attrname, value)
class _swapped_struct_meta(_swapped_meta, type(Structure)): pass
class _swapped_union_meta(_swapped_meta, type(Union)): pass
################################################################
@ -43,19 +45,34 @@ if sys.byteorder == "little":
LittleEndianStructure = Structure
class BigEndianStructure(Structure, metaclass=_swapped_meta):
class BigEndianStructure(Structure, metaclass=_swapped_struct_meta):
"""Structure with big endian byte order"""
__slots__ = ()
_swappedbytes_ = None
LittleEndianUnion = Union
class BigEndianUnion(Union, metaclass=_swapped_union_meta):
"""Union with big endian byte order"""
__slots__ = ()
_swappedbytes_ = None
elif sys.byteorder == "big":
_OTHER_ENDIAN = "__ctype_le__"
BigEndianStructure = Structure
class LittleEndianStructure(Structure, metaclass=_swapped_meta):
class LittleEndianStructure(Structure, metaclass=_swapped_struct_meta):
"""Structure with little endian byte order"""
__slots__ = ()
_swappedbytes_ = None
BigEndianUnion = Union
class LittleEndianUnion(Union, metaclass=_swapped_union_meta):
"""Union with little endian byte order"""
__slots__ = ()
_swappedbytes_ = None
else:
raise RuntimeError("Invalid byteorder")

View File

@ -170,40 +170,34 @@ class Test(unittest.TestCase):
self.assertIs(c_char.__ctype_le__, c_char)
self.assertIs(c_char.__ctype_be__, c_char)
def test_struct_fields_1(self):
if sys.byteorder == "little":
base = BigEndianStructure
else:
base = LittleEndianStructure
def test_struct_fields_unsupported_byte_order(self):
class T(base):
pass
_fields_ = [("a", c_ubyte),
("b", c_byte),
("c", c_short),
("d", c_ushort),
("e", c_int),
("f", c_uint),
("g", c_long),
("h", c_ulong),
("i", c_longlong),
("k", c_ulonglong),
("l", c_float),
("m", c_double),
("n", c_char),
("b1", c_byte, 3),
("b2", c_byte, 3),
("b3", c_byte, 2),
("a", c_int * 3 * 3 * 3)]
T._fields_ = _fields_
fields = [
("a", c_ubyte),
("b", c_byte),
("c", c_short),
("d", c_ushort),
("e", c_int),
("f", c_uint),
("g", c_long),
("h", c_ulong),
("i", c_longlong),
("k", c_ulonglong),
("l", c_float),
("m", c_double),
("n", c_char),
("b1", c_byte, 3),
("b2", c_byte, 3),
("b3", c_byte, 2),
("a", c_int * 3 * 3 * 3)
]
# these fields do not support different byte order:
for typ in c_wchar, c_void_p, POINTER(c_int):
_fields_.append(("x", typ))
class T(base):
pass
self.assertRaises(TypeError, setattr, T, "_fields_", [("x", typ)])
with self.assertRaises(TypeError):
class T(BigEndianStructure if sys.byteorder == "little" else LittleEndianStructure):
_fields_ = fields + [("x", typ)]
def test_struct_struct(self):
# nested structures with different byteorders
@ -233,7 +227,7 @@ class Test(unittest.TestCase):
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)
def test_struct_fields_2(self):
def test_struct_field_alignment(self):
# standard packing in struct uses no alignment.
# So, we have to align using pad bytes.
#
@ -267,7 +261,6 @@ class Test(unittest.TestCase):
class S(base):
_pack_ = 1
_fields_ = [("b", c_byte),
("h", c_short),
("_1", c_byte),
@ -311,5 +304,61 @@ class Test(unittest.TestCase):
s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14)
self.assertEqual(bin(s1), bin(s2))
def test_union_fields_unsupported_byte_order(self):
fields = [
("a", c_ubyte),
("b", c_byte),
("c", c_short),
("d", c_ushort),
("e", c_int),
("f", c_uint),
("g", c_long),
("h", c_ulong),
("i", c_longlong),
("k", c_ulonglong),
("l", c_float),
("m", c_double),
("n", c_char),
("b1", c_byte, 3),
("b2", c_byte, 3),
("b3", c_byte, 2),
("a", c_int * 3 * 3 * 3)
]
# these fields do not support different byte order:
for typ in c_wchar, c_void_p, POINTER(c_int):
with self.assertRaises(TypeError):
class T(BigEndianUnion if sys.byteorder == "little" else LittleEndianUnion):
_fields_ = fields + [("x", typ)]
def test_union_struct(self):
# nested structures in unions with different byteorders
# create nested structures in unions with given byteorders and set memory to data
for nested, data in (
(BigEndianStructure, b'\0\0\0\1\0\0\0\2'),
(LittleEndianStructure, b'\1\0\0\0\2\0\0\0'),
):
for parent in (
BigEndianUnion,
LittleEndianUnion,
Union,
):
class NestedStructure(nested):
_fields_ = [("x", c_uint32),
("y", c_uint32)]
class TestUnion(parent):
_fields_ = [("point", NestedStructure)]
self.assertEqual(len(data), sizeof(TestUnion))
ptr = POINTER(TestUnion)
s = cast(data, ptr)[0]
del ctypes._pointer_type_cache[TestUnion]
self.assertEqual(s.point.x, 1)
self.assertEqual(s.point.y, 2)
if __name__ == "__main__":
unittest.main()

View File

@ -0,0 +1 @@
Added :class:`ctypes.BigEndianUnion` and :class:`ctypes.LittleEndianUnion` classes, as originally documented in the library docs but not yet implemented.