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 c67f25e

Browse filesBrowse files
encukouseehwan80
authored andcommitted
pythongh-128715: Expose ctypes.CField, with info attributes (pythonGH-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 3744b06 commit c67f25e
Copy full SHA for c67f25e
Expand file treeCollapse file tree

27 files changed

+907
-227
lines changed

‎Doc/library/ctypes.rst

Copy file name to clipboardExpand all lines: Doc/library/ctypes.rst
+98-5Lines 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

659659
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

Copy file name to clipboardExpand all lines: Doc/whatsnew/3.14.rst
+5Lines 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

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_objects_fini_generated.h
+4Lines 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

Copy file name to clipboardExpand all lines: Include/internal/pycore_global_strings.h
+4Lines 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

Copy file name to clipboardExpand all lines: Include/internal/pycore_runtime_init_generated.h
+4Lines 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

Copy file name to clipboardExpand all lines: Include/internal/pycore_unicodeobject_generated.h
+16Lines 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

Copy file name to clipboardExpand all lines: Lib/ctypes/__init__.py
+1Lines 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

Copy file name to clipboardExpand all lines: Lib/ctypes/_layout.py
+15-37Lines 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)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.