8000 gh-128715: Expose ctypes.CField, with info attributes (GH-128950) · python/cpython@0e53038 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0e53038

Browse files
authored
gh-128715: Expose ctypes.CField, with info attributes (GH-128950)
- Restore max field size to sys.maxsize, as in Python 3.13 & below - PyCField: Split out bit/byte sizes/offsets. - Expose CField's size/offset data to Python code - Add generic checks for all the test structs/unions, using the newly exposed attrs
1 parent 62fb15d commit 0e53038

27 files changed

+907
-227
lines changed

Doc/library/ctypes.rst

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -657,12 +657,13 @@ Nested structures can also be initialized in the constructor in several ways::
657657
>>> r = RECT((1, 2), (3, 4))
658658

65965 8000 9
Field :term:`descriptor`\s can be retrieved from the *class*, they are useful
660-
for debugging because they can provide useful information::
660+
for debugging because they can provide useful information.
661+
See :class:`CField`::
661662

662-
>>> print(POINT.x)
663-
<Field type=c_long, ofs=0, size=4>
664-
>>> print(POINT.y)
665-
<Field type=c_long, ofs=4, size=4>
663+
>>> POINT.x
664+
<ctypes.CField 'x' type=c_int, ofs=0, size=4>
665+
>>> POINT.y
666+
<ctypes.CField 'y' type=c_int, ofs=4, size=4>
666667
>>>
667668

668669

@@ -2812,6 +2813,98 @@ fields, or any other data types containing pointer type fields.
28122813
present in :attr:`_fields_`.
28132814

28142815

2816+
.. class:: CField(*args, **kw)
2817+
2818+
Descriptor for fields of a :class:`Structure` and :class:`Union`.
2819+
For example::
2820+
2821+
>>> class Color(Structure):
2822+
... _fields_ = (
2823+
... ('red', c_uint8),
2824+
... ('green', c_uint8),
2825+
... ('blue', c_uint8),
2826+
... ('intense', c_bool, 1),
2827+
... ('blinking', c_bool, 1),
2828+
... )
2829+
...
2830+
>>> Color.red
2831+
<ctypes.CField 'red' type=c_ubyte, ofs=0, size=1>
2832+
>>> Color.green.type
2833+
<class 'ctypes.c_ubyte'>
2834+
>>> Color.blue.byte_offset
2835+
2
2836+
>>> Color.intense
2837+
<ctypes.CField 'intense' type=c_bool, ofs=3, bit_size=1, bit_offset=0>
2838+
>>> Color.blinking.bit_offset
2839+
1
2840+
2841+
All attributes are read-only.
2842+
2843+
:class:`!CField` objects are created via :attr:`~Structure._fields_`;
2844+
do not instantiate the class directly.
2845+
2846+
.. versionadded:: next
2847+
2848+
Previously, descriptors only had ``offset`` and ``size`` attributes
2849+
and a readable string representation; the :class:`!CField` class was not
2850+
available directly.
2851+
2852+
.. attribute:: name
2853+
2854+
Name of the field, as a string.
2855+
2856+
.. attribute:: type
2857+
2858+
Type of the field, as a :ref:`ctypes class <ctypes-data-types>`.
2859+
2860+
.. attribute:: offset
2861+
byte_offset
2862+
2863+
Offset of the field, in bytes.
2864+
2865+
For bitfields, this is the offset of the underlying byte-aligned
2866+
*storage unit*; see :attr:`~CField.bit_offset`.
2867+
2868+
.. attribute:: byte_size
2869+
2870+
Size of the field, in bytes.
2871+
2872+
For bitfields, this is the size of the underlying *storage unit*.
2873+
Typically, it has the same size as the bitfield's type.
2874+
2875+
.. attribute:: size
2876+
2877+
For non-bitfields, equivalent to :attr:`~CField.byte_size`.
2878+
2879+
For bitfields, this contains a backwards-compatible bit-packed
2880+
value that combines :attr:`~CField.bit_size` and
2881+
:attr:`~CField.bit_offset`.
2882+
Prefer using the explicit attributes instead.
2883+
2884+
.. attribute:: is_bitfield
2885+
2886+
True if this is a bitfield.
2887+
2888+
.. attribute:: bit_offset
2889+
bit_size
2890+
2891+
The location of a bitfield within its *storage unit*, that is, within
2892+
:attr:`~CField.byte_size` bytes of memory starting at
2893+
:attr:`~CField.byte_offset`.
2894+
2895+
To get the field's value, read the storage unit as an integer,
2896+
:ref:`shift left <shifting>` by :attr:`!bit_offset` and
2897+
take the :attr:`!bit_size` least significant bits.
2898+
2899+
For non-bitfields, :attr:`!bit_offset` is zero
2900+
and :attr:`!bit_size` is equal to ``byte_size * 8``.
2901+
2902+
.. attribute:: is_anonymous
2903+
2904+
True if this field is anonymous, that is, it contains nested sub-fields
2905+
that should be be merged into a containing structure or union.
2906+
2907+
28152908
.. _ctypes-arrays-pointers:
28162909

28172910
Arrays and pointers

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,11 @@ ctypes
502502
to help match a non-default ABI.
503503
(Contributed by Petr Viktorin in :gh:`97702`.)
504504

505+
* The class of :class:`~ctypes.Structure`/:class:`~ctypes.Union`
506+
field descriptors is now available as :class:`~ctypes.CField`,
507+
and has new attributes to aid debugging and introspection.
508+
(Contributed by Petr Viktorin in :gh:`128715`.)
509+
505510
* On Windows, the :exc:`~ctypes.COMError` exception is now public.
506511
(Contributed by Jun Komoda in :gh:`126686`.)
507512

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 4 additions & 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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ struct _Py_global_strings {
247247
STRUCT_FOR_ID(_get_sourcefile)
248248
STRUCT_FOR_ID(_handle_fromlist)
249249
STRUCT_FOR_ID(_initializing)
250+
STRUCT_FOR_ID(_internal_use)
250251
STRUCT_FOR_ID(_io)
251252
STRUCT_FOR_ID(_is_text_encoding)
252253
STRUCT_FOR_ID(_isatty_open_only)
@@ -297,6 +298,7 @@ struct _Py_global_strings {
297298
STRUCT_FOR_ID(before)
298299
STRUCT_FOR_ID(big)
299300
STRUCT_FOR_ID(binary_form)
301+
STRUCT_FOR_ID(bit_offset)
300302
STRUCT_FOR_ID(bit_size)
301303
STRUCT_FOR_ID(block)
302304
STRUCT_FOR_ID(bound)
@@ -307,6 +309,8 @@ struct _Py_global_strings {
307309
STRUCT_FOR_ID(buffers)
308310
STRUCT_FOR_ID(bufsize)
309311
STRUCT_FOR_ID(builtins)
312+
STRUCT_FOR_ID(byte_offset)
313+
STRUCT_FOR_ID(byte_size)
310314
STRUCT_FOR_ID(byteorder)
311315
STRUCT_FOR_ID(bytes)
312316
STRUCT_FOR_ID(bytes_per_sep)

Include/internal/pycore_runtime_init_generated.h

Lines changed: 4 additions & 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: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Lib/ctypes/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from _ctypes import RTLD_LOCAL, RTLD_GLOBAL
1313
from _ctypes import ArgumentError
1414
from _ctypes import SIZEOF_TIME_T
15+
from _ctypes import CField
1516

1617
from struct import calcsize as _calcsize
1718

Lib/ctypes/_layout.py

Lines changed: 15 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,6 @@ def round_up(n, multiple):
1919
assert multiple > 0
2020
return ((n + multiple - 1) // multiple) * multiple
2121

22-
def LOW_BIT(offset):
23-
return offset & 0xFFFF
24-
25-
def NUM_BITS(bitsize):
26-
return bitsize >> 16
27-
28-
def BUILD_SIZE(bitsize, offset):
29-
assert 0 <= offset, offset
30-
assert offset <= 0xFFFF, offset
31-
# We don't support zero length bitfields.
32-
# And GET_BITFIELD uses NUM_BITS(size) == 0,
33-
# to figure out whether we are handling a bitfield.
34-
assert bitsize > 0, bitsize
35-
result = (bitsize << 16) + offset
36-
assert bitsize == NUM_BITS(result), (bitsize, result)
37-
assert offset == LOW_BIT(result), (offset, result)
38-
return result
39-
40-
def build_size(bit_size, bit_offset, big_endian, type_size):
41-
if big_endian:
42-
return BUILD_SIZE(bit_size, 8 * type_size - bit_offset - bit_size)
43-
return BUILD_SIZE(bit_size, bit_offset)
44-
4522
_INT_MAX = (1 << (ctypes.sizeof(ctypes.c_int) * 8) - 1) - 1
4623

4724

@@ -213,13 +190,10 @@ def get_layout(cls, input_fields, is_struct, base):
213190

214191
offset = round_down(next_bit_offset, type_bit_align) // 8
215192
if is_bitfield:
216-
effective_bit_offset = next_bit_offset - 8 * offset
217-
size = build_size(bit_size, effective_bit_offset,
218-
big_endian, type_size)
219-
assert effective_bit_offset <= type_bit_size
193+
bit_offset = next_bit_offset - 8 * offset
194+
assert bit_offset <= type_bit_size
220195
else:
221196
assert offset == next_bit_offset / 8
222-
size = type_size
223197

224198
next_bit_offset += bit_size
225199
struct_size = round_up(next_bit_offset, 8) // 8
@@ -253,18 +227,17 @@ def get_layout(cls, input_fields, is_struct, base):
253227
offset = next_byte_offset - last_field_bit_size // 8
254228
if is_bitfield:
255229
assert 0 <= (last_field_bit_size + next_bit_offset)
256-
size = build_size(bit_size,
257-
last_field_bit_size + next_bit_offset,
258-
big_endian, type_size)
259-
else:
260-
size = type_size
230+
bit_offset = last_field_bit_size + next_bit_offset
261231
if type_bit_size:
262232
assert (last_field_bit_size + next_bit_offset) < type_bit_size
263233

264234
next_bit_offset += bit_size
265235
struct_size = next_byte_offset
266236

267-
assert (not is_bitfield) or (LOW_BIT(size) <= size * 8)
237+
if is_bitfield and big_endian:
238+
# On big-endian architectures, bit fields are also laid out
239+
# starting with the big end.
240+
bit_offset = type_bit_size - bit_size - bit_offset
268241

269242
# Add the format spec parts
270243
if is_struct:
@@ -286,16 +259,21 @@ def get_layout(cls, input_fields, is_struct, base):
286259
# a bytes name would be rejected later, but we check early
287260
# to avoid a BytesWarning with `python -bb`
288261
raise TypeError(
289-
"field {name!r}: name must be a string, not bytes")
262+
f"field {name!r}: name must be a string, not bytes")
290263
format_spec_parts.append(f"{fieldfmt}:{name}:")
291264

292265
result_fields.append(CField(
293266
name=name,
294267
type=ctype,
295-
size=size,
296-
offset=offset,
268+
byte_size=type_size,
269+
byte_offset=offset,
297270
bit_size=bit_size if is_bitfield else None,
271+
bit_offset=bit_offset if is_bitfield else None,
298272
index=i,
273+
274+
# Do not use CField outside ctypes, yet.
275+
# The constructor is internal API and may change without warning.
276+
_internal_use=True,
299277
))
300278
if is_bitfield and not gcc_layout:
301279
assert type_bit_size > 0

0 commit comments

Comments
 (0)
0