8000 bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes by ringof · Pull Request #25480 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content

bpo-33178: Add BigEndianUnion, LittleEndianUnion classes to ctypes #25480

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 9 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
14 changes: 12 additions & 2 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2391,6 +2391,16 @@ 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.


.. class:: LittleEndianUnion(*args, **kw)

Abstract base class for unions in *little endian* byte order.


.. class:: BigEndianStructure(*args, **kw)

Abstract base class for structures in *big endian* byte order.
Expand All @@ -2400,8 +2410,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)
Expand Down
1 change: 1 addition & 0 deletions Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ def DllCanUnloadNow():
return ccom.DllCanUnloadNow()

from ctypes._endian import BigEndianStructure, LittleEndianStructure
from ctypes._endian import BigEndianUnion, LittleEndianUnion

# Fill in specifically-sized types
c_int8 = c_byte
Expand Down
23 changes: 20 additions & 3 deletions Lib/ctypes/_endian.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ 8000 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 = []
Expand All @@ -31,6 +31,8 @@ def __setattr__(self, attrname, value):
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

################################################################

Expand All @@ -43,19 +45,34 @@ def __setattr__(self, attrname, value):

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")
115 changes: 89 additions & 26 deletions Lib/ctypes/test/test_byteswap.py
67ED
Original file line number Diff line number Diff line change
Expand Up @@ -170,40 +170,41 @@ def test_endian_other(self):
self.assertIs(c_char.__ctype_le__, c_char)
self.assertIs(c_char.__ctype_be__, c_char)

def test_struct_fields_1(self):
def test_struct_fields_unsupported_byte_order(self):
if sys.byteorder == "little":
base = BigEndianStructure
else:
base = LittleEndianStructure

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(base):
_fields_ = fields + [("x", typ)]


def test_struct_struct(self):
# nested structures with different byteorders
Expand Down Expand Up @@ -233,7 +234,7 @@ class TestStructure(parent):
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.
#
Expand Down Expand Up @@ -267,7 +268,6 @@ def test_unaligned_nonnative_struct_fields(self):
class S(base):
_pack_ = 1
_fields_ = [("b", c_byte),

("h", c_short),

("_1", c_byte),
Expand Down Expand Up @@ -311,5 +311,68 @@ class S(Structure):
s2 = struct.pack(fmt, 0x12, 0x1234, 0x12345678, 3.14)
self.assertEqual(bin(s1), bin(s2))

def test_union_fields_unsupported_byte_order(self):
if sys.byteorder == "little":
base = BigEndianUnion
else:
base = LittleEndianUnion

class T(base):
pass

fields = [
("a", c_ubyte),
("b", c_byte),
A3DB ("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(base):
_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()
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added :class:`ctypes.BigEndianUnion` and :class:`ctypes.LittleEndianUnion` classes, as originally documented in the library docs but not yet implemented.
0