From add1ed5064ca3e29a4ac21a3c5759d1f43180181 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 00:44:59 +0200 Subject: [PATCH 1/8] gh-104533: Use @ctypes.util.struct decorator Use also @ctypes.util.wrap_dll_function() decorator. --- Lib/_pyrepl/windows_console.py | 77 ++++++++++---------- Lib/ctypes/wintypes.py | 115 ++++++++++++++++-------------- Lib/test/_test_multiprocessing.py | 16 ++--- Lib/test/support/__init__.py | 38 +++++++--- Lib/test/test_buffer.py | 22 ++++-- Lib/test/test_codecs.py | 20 +++--- Lib/test/test_io/utils.py | 5 +- 7 files changed, 166 insertions(+), 127 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 3768a22ad16f7b..053682810d4a7a 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -23,7 +23,7 @@ import os import sys -import ctypes +import ctypes.util import types from dataclasses import dataclass from ctypes.wintypes import ( @@ -37,7 +37,7 @@ WCHAR, SHORT, ) -from ctypes import Structure, POINTER, Union +from ctypes import POINTER, Union from typing import TYPE_CHECKING from _colorize import ANSIColors @@ -723,61 +723,56 @@ def repaint(self) -> None: # Windows interop -class CONSOLE_SCREEN_BUFFER_INFO(Structure): - _fields_ = [ - ("dwSize", _COORD), - ("dwCursorPosition", _COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", _COORD), - ] +@ctypes.util.struct +class CONSOLE_SCREEN_BUFFER_INFO: + dwSize: _COORD + dwCursorPosition: _COORD + wAttributes: WORD + srWindow: SMALL_RECT + dwMaximumWindowSize: _COORD -class CONSOLE_CURSOR_INFO(Structure): - _fields_ = [ - ("dwSize", DWORD), - ("bVisible", BOOL), - ] +@ctypes.util.struct +class CONSOLE_CURSOR_INFO: + dwSize: DWORD + bVisible: BOOL -class CHAR_INFO(Structure): - _fields_ = [ - ("UnicodeChar", WCHAR), - ("Attributes", WORD), - ] +@ctypes.util.struct +class CHAR_INFO: + UnicodeChar: WCHAR + Attributes: WORD class Char(Union): - _fields_ = [ - ("UnicodeChar", WCHAR), - ("Char", CHAR), - ] + UnicodeChar: WCHAR + Char: CHAR -class KeyEvent(ctypes.Structure): - _fields_ = [ - ("bKeyDown", BOOL), - ("wRepeatCount", WORD), - ("wVirtualKeyCode", WORD), - ("wVirtualScanCode", WORD), - ("uChar", Char), - ("dwControlKeyState", DWORD), - ] +@ctypes.util.struct +class KeyEvent: + bKeyDown: BOOL + wRepeatCount: WORD + wVirtualKeyCode: WORD + wVirtualScanCode: WORD + uChar: Char + dwControlKeyState: DWORD -class WindowsBufferSizeEvent(ctypes.Structure): - _fields_ = [("dwSize", _COORD)] +@ctypes.util.struct +class WindowsBufferSizeEvent: + dwSize: _COORD class ConsoleEvent(ctypes.Union): - _fields_ = [ - ("KeyEvent", KeyEvent), - ("WindowsBufferSizeEvent", WindowsBufferSizeEvent), - ] + KeyEvent: KeyEvent + WindowsBufferSizeEvent: WindowsBufferSizeEvent -class INPUT_RECORD(Structure): - _fields_ = [("EventType", WORD), ("Event", ConsoleEvent)] +@ctypes.util.struct +class INPUT_RECORD: + EventType: WORD + Event: ConsoleEvent KEY_EVENT = 0x01 diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py index 4beba0d19513e2..3dc01ab267fbe8 100644 --- a/Lib/ctypes/wintypes.py +++ b/Lib/ctypes/wintypes.py @@ -1,5 +1,5 @@ # The most useful windows datatypes -import ctypes +import ctypes.util BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort @@ -102,75 +102,84 @@ def __repr__(self): ################################################################ # Some important structure definitions -class RECT(ctypes.Structure): - _fields_ = [("left", LONG), - ("top", LONG), - ("right", LONG), - ("bottom", LONG)] +@ctypes.util.struct +class RECT: + left: LONG + top: LONG + right: LONG + bottom: LONG tagRECT = _RECTL = RECTL = RECT -class _SMALL_RECT(ctypes.Structure): - _fields_ = [('Left', SHORT), - ('Top', SHORT), - ('Right', SHORT), - ('Bottom', SHORT)] +@ctypes.util.struct +class _SMALL_RECT: + Left: SHORT + Top: SHORT + Right: SHORT + Bottom: SHORT SMALL_RECT = _SMALL_RECT -class _COORD(ctypes.Structure): - _fields_ = [('X', SHORT), - ('Y', SHORT)] +@ctypes.util.struct +class _COORD: + X: SHORT + Y: SHORT -class POINT(ctypes.Structure): - _fields_ = [("x", LONG), - ("y", LONG)] +@ctypes.util.struct +class POINT: + x: LONG + y: LONG tagPOINT = _POINTL = POINTL = POINT -class SIZE(ctypes.Structure): - _fields_ = [("cx", LONG), - ("cy", LONG)] +@ctypes.util.struct +class SIZE: + cx: LONG + cy: LONG tagSIZE = SIZEL = SIZE def RGB(red, green, blue): return red + (green << 8) + (blue << 16) -class FILETIME(ctypes.Structure): - _fields_ = [("dwLowDateTime", DWORD), - ("dwHighDateTime", DWORD)] +@ctypes.util.struct +class FILETIME: + dwLowDateTime: DWORD + dwHighDateTime: DWORD _FILETIME = FILETIME -class MSG(ctypes.Structure): - _fields_ = [("hWnd", HWND), - ("message", UINT), - ("wParam", WPARAM), - ("lParam", LPARAM), - ("time", DWORD), - ("pt", POINT)] +@ctypes.util.struct +class MSG: + hWnd: HWND + message: UINT + wParam: WPARAM + lParam: LPARAM + time: DWORD + pt: POINT tagMSG = MSG MAX_PATH = 260 -class WIN32_FIND_DATAA(ctypes.Structure): - _fields_ = [("dwFileAttributes", DWORD), - ("ftCreationTime", FILETIME), - ("ftLastAccessTime", FILETIME), - ("ftLastWriteTime", FILETIME), - ("nFileSizeHigh", DWORD), - ("nFileSizeLow", DWORD), - ("dwReserved0", DWORD), - ("dwReserved1", DWORD), - ("cFileName", CHAR * MAX_PATH), - ("cAlternateFileName", CHAR * 14)] - -class WIN32_FIND_DATAW(ctypes.Structure): - _fields_ = [("dwFileAttributes", DWORD), - ("ftCreationTime", FILETIME), - ("ftLastAccessTime", FILETIME), - ("ftLastWriteTime", FILETIME), - ("nFileSizeHigh", DWORD), - ("nFileSizeLow", DWORD), - ("dwReserved0", DWORD), - ("dwReserved1", DWORD), - ("cFileName", WCHAR * MAX_PATH), - ("cAlternateFileName", WCHAR * 14)] +@ctypes.util.struct +class WIN32_FIND_DATAA: + dwFileAttributes: DWORD + ftCreationTime: FILETIME + ftLastAccessTime: FILETIME + ftLastWriteTime: FILETIME + nFileSizeHigh: DWORD + nFileSizeLow: DWORD + dwReserved0: DWORD + dwReserved1: DWORD + cFileName: CHAR * MAX_PATH + cAlternateFileName: CHAR * 14 + +@ctypes.util.struct +class WIN32_FIND_DATAW: + dwFileAttributes: DWORD + ftCreationTime: FILETIME + ftLastAccessTime: FILETIME + ftLastWriteTime: FILETIME + nFileSizeHigh: DWORD + nFileSizeLow: DWORD + dwReserved0: DWORD + dwReserved1: DWORD + cFileName: WCHAR * MAX_PATH + cAlternateFileName: WCHAR * 14 ################################################################ # Pointer types diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py index 115a187a8a8588..8f665b3a98a037 100644 --- a/Lib/test/_test_multiprocessing.py +++ b/Lib/test/_test_multiprocessing.py @@ -161,9 +161,10 @@ def wait_for_handle(handle, timeout): # try: - from ctypes import Structure, c_int, c_double, c_longlong + from ctypes.util import struct as ctypes_struct + from ctypes import c_int, c_double, c_longlong except ImportError: - Structure = object + def ctypes_struct(cls): return cls c_int = c_double = c_longlong = None @@ -4390,12 +4391,11 @@ def test_free_from_gc(self): # # -class _Foo(Structure): - _fields_ = [ - ('x', c_int), - ('y', c_double), - ('z', c_longlong,) - ] +@ctypes_struct +class _Foo: + x: c_int + y: c_double + z: c_longlong class _TestSharedCTypes(BaseTestCase): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index d17d9a2ecf8d9b..86972ea9c4e78f 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -236,24 +236,44 @@ def _is_gui_available(): # if Python is running as a service (such as the buildbot service), # gui interaction may be disallowed import ctypes + import ctypes.util import ctypes.wintypes + UOI_FLAGS = 1 WSF_VISIBLE = 0x0001 - class USEROBJECTFLAGS(ctypes.Structure): - _fields_ = [("fInherit", ctypes.wintypes.BOOL), - ("fReserved", ctypes.wintypes.BOOL), - ("dwFlags", ctypes.wintypes.DWORD)] - dll = ctypes.windll.user32 - h = dll.GetProcessWindowStation() + + @ctypes.util.struct + class USEROBJECTFLAGS: + fInherit: ctypes.wintypes.BOOL + fReserved: ctypes.wintypes.BOOL + dwFlags: ctypes.wintypes.DWORD + + user32 = ctypes.windll.user32 + + @ctypes.util.wrap_dll_function(user32) + def GetProcessWindowStation() -> ctypes.wintypes.HANDLE: + ... + + h = GetProcessWindowStation() if not h: raise ctypes.WinError() + + def GetUserObjectInformationW( + hObj: ctypes.wintypes.HANDLE, + nIndex: ctypes.c_int, + pvInfo: ctypes.c_void_p, + nLength: ctypes.wintypes.DWORD, + lpnLengthNeeded: ctypes.POINTER(ctypes.wintypes.DWORD), + ) -> ctypes.wintypes.BOOL: + ... + uof = USEROBJECTFLAGS() needed = ctypes.wintypes.DWORD() - res = dll.GetUserObjectInformationW(h, + res = GetUserObjectInformationW(h, UOI_FLAGS, - ctypes.byref(uof), + uof, ctypes.sizeof(uof), - ctypes.byref(needed)) + needed) if not res: raise ctypes.WinError() if not bool(uof.dwFlags & WSF_VISIBLE): diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 3213a475127343..453dafe2709eb2 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -37,6 +37,7 @@ try: import ctypes + import ctypes.util except ImportError: ctypes = None @@ -2849,8 +2850,11 @@ def test_memoryview_cast_1D_ND(self): if ctypes: # format: "T{>l:x:>d:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_double + point = BEPoint(100, 200.1) m1 = memoryview(point) m2 = m1.cast('B') @@ -3250,8 +3254,11 @@ def test_memoryview_compare_special_cases(self): # Some ctypes format strings are unknown to the struct module. if ctypes: # format: "T{>l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_long + point = BEPoint(100, 200) a = memoryview(point) b = memoryview(point) @@ -3988,8 +3995,11 @@ def test_memoryview_tobytes(self): # Unknown formats are handled: tobytes() purely depends on itemsize. if ctypes: # format: "T{>l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_long + point = BEPoint(100, 200) a = memoryview(point) self.assertEqual(a.tobytes(), bytes(point)) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index c18b203f42f59f..bc6f9b31e06c80 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -36,21 +36,25 @@ def check(input, expect): self.assertEqual(coder(input), (expect, len(input))) return check -# On small versions of Windows like Windows IoT or Windows Nano Server not all codepages are present +# On small versions of Windows like Windows IoT or Windows Nano Server, +# not all codepages are present def is_code_page_present(cp): from ctypes import POINTER, WINFUNCTYPE, WinDLL, Structure + from ctypes.util import struct from ctypes.wintypes import BOOL, BYTE, WCHAR, UINT, DWORD MAX_LEADBYTES = 12 # 5 ranges, 2 bytes ea., 0 term. MAX_DEFAULTCHAR = 2 # single or double byte MAX_PATH = 260 - class CPINFOEXW(Structure): - _fields_ = [("MaxCharSize", UINT), - ("DefaultChar", BYTE*MAX_DEFAULTCHAR), - ("LeadByte", BYTE*MAX_LEADBYTES), - ("UnicodeDefaultChar", WCHAR), - ("CodePage", UINT), - ("CodePageName", WCHAR*MAX_PATH)] + + @struct + class CPINFOEXW: + MaxCharSize: UINT + DefaultChar: BYTE * MAX_DEFAULTCHAR + LeadByte: BYTE * MAX_LEADBYTES + UnicodeDefaultChar: WCHAR + CodePage: UINT + CodePageName: WCHAR * MAX_PATH prototype = WINFUNCTYPE(BOOL, UINT, DWORD, POINTER(CPINFOEXW)) GetCPInfoEx = prototype(("GetCPInfoExW", WinDLL("kernel32"))) diff --git a/Lib/test/test_io/utils.py b/Lib/test/test_io/utils.py index 3b1faec2140fbc..dde49337a24f0b 100644 --- a/Lib/test/test_io/utils.py +++ b/Lib/test/test_io/utils.py @@ -8,12 +8,13 @@ try: - import ctypes + import ctypes.util except ImportError: def byteslike(*pos, **kw): return array.array("b", bytes(*pos, **kw)) else: - class EmptyStruct(ctypes.Structure): + @ctypes.util.struct + class EmptyStruct: pass def byteslike(*pos, **kw): From 05c337815a14302bc05ff1dc6b9dba381ba4f681 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 01:24:01 +0200 Subject: [PATCH 2/8] Revert changes --- Lib/_pyrepl/windows_console.py | 77 +++++++++++----------- Lib/ctypes/wintypes.py | 115 +++++++++++++++------------------ Lib/test/test_buffer.py | 22 ++----- 3 files changed, 100 insertions(+), 114 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 053682810d4a7a..3768a22ad16f7b 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -23,7 +23,7 @@ import os import sys -import ctypes.util +import ctypes import types from dataclasses import dataclass from ctypes.wintypes import ( @@ -37,7 +37,7 @@ WCHAR, SHORT, ) -from ctypes import POINTER, Union +from ctypes import Structure, POINTER, Union from typing import TYPE_CHECKING from _colorize import ANSIColors @@ -723,56 +723,61 @@ def repaint(self) -> None: # Windows interop -@ctypes.util.struct -class CONSOLE_SCREEN_BUFFER_INFO: - dwSize: _COORD - dwCursorPosition: _COORD - wAttributes: WORD - srWindow: SMALL_RECT - dwMaximumWindowSize: _COORD +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", _COORD), + ("dwCursorPosition", _COORD), + ("wAttributes", WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", _COORD), + ] -@ctypes.util.struct -class CONSOLE_CURSOR_INFO: - dwSize: DWORD - bVisible: BOOL +class CONSOLE_CURSOR_INFO(Structure): + _fields_ = [ + ("dwSize", DWORD), + ("bVisible", BOOL), + ] -@ctypes.util.struct -class CHAR_INFO: - UnicodeChar: WCHAR - Attributes: WORD +class CHAR_INFO(Structure): + _fields_ = [ + ("UnicodeChar", WCHAR), + ("Attributes", WORD), + ] class Char(Union): - UnicodeChar: WCHAR - Char: CHAR + _fields_ = [ + ("UnicodeChar", WCHAR), + ("Char", CHAR), + ] -@ctypes.util.struct -class KeyEvent: - bKeyDown: BOOL - wRepeatCount: WORD - wVirtualKeyCode: WORD - wVirtualScanCode: WORD - uChar: Char - dwControlKeyState: DWORD +class KeyEvent(ctypes.Structure): + _fields_ = [ + ("bKeyDown", BOOL), + ("wRepeatCount", WORD), + ("wVirtualKeyCode", WORD), + ("wVirtualScanCode", WORD), + ("uChar", Char), + ("dwControlKeyState", DWORD), + ] -@ctypes.util.struct -class WindowsBufferSizeEvent: - dwSize: _COORD +class WindowsBufferSizeEvent(ctypes.Structure): + _fields_ = [("dwSize", _COORD)] class ConsoleEvent(ctypes.Union): - KeyEvent: KeyEvent - WindowsBufferSizeEvent: WindowsBufferSizeEvent + _fields_ = [ + ("KeyEvent", KeyEvent), + ("WindowsBufferSizeEvent", WindowsBufferSizeEvent), + ] -@ctypes.util.struct -class INPUT_RECORD: - EventType: WORD - Event: ConsoleEvent +class INPUT_RECORD(Structure): + _fields_ = [("EventType", WORD), ("Event", ConsoleEvent)] KEY_EVENT = 0x01 diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py index 3dc01ab267fbe8..4beba0d19513e2 100644 --- a/Lib/ctypes/wintypes.py +++ b/Lib/ctypes/wintypes.py @@ -1,5 +1,5 @@ # The most useful windows datatypes -import ctypes.util +import ctypes BYTE = ctypes.c_ubyte WORD = ctypes.c_ushort @@ -102,84 +102,75 @@ def __repr__(self): ################################################################ # Some important structure definitions -@ctypes.util.struct -class RECT: - left: LONG - top: LONG - right: LONG - bottom: LONG +class RECT(ctypes.Structure): + _fields_ = [("left", LONG), + ("top", LONG), + ("right", LONG), + ("bottom", LONG)] tagRECT = _RECTL = RECTL = RECT -@ctypes.util.struct -class _SMALL_RECT: - Left: SHORT - Top: SHORT - Right: SHORT - Bottom: SHORT +class _SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', SHORT), + ('Top', SHORT), + ('Right', SHORT), + ('Bottom', SHORT)] SMALL_RECT = _SMALL_RECT -@ctypes.util.struct -class _COORD: - X: SHORT - Y: SHORT +class _COORD(ctypes.Structure): + _fields_ = [('X', SHORT), + ('Y', SHORT)] -@ctypes.util.struct -class POINT: - x: LONG - y: LONG +class POINT(ctypes.Structure): + _fields_ = [("x", LONG), + ("y", LONG)] tagPOINT = _POINTL = POINTL = POINT -@ctypes.util.struct -class SIZE: - cx: LONG - cy: LONG +class SIZE(ctypes.Structure): + _fields_ = [("cx", LONG), + ("cy", LONG)] tagSIZE = SIZEL = SIZE def RGB(red, green, blue): return red + (green << 8) + (blue << 16) -@ctypes.util.struct -class FILETIME: - dwLowDateTime: DWORD - dwHighDateTime: DWORD +class FILETIME(ctypes.Structure): + _fields_ = [("dwLowDateTime", DWORD), + ("dwHighDateTime", DWORD)] _FILETIME = FILETIME -@ctypes.util.struct -class MSG: - hWnd: HWND - message: UINT - wParam: WPARAM - lParam: LPARAM - time: DWORD - pt: POINT +class MSG(ctypes.Structure): + _fields_ = [("hWnd", HWND), + ("message", UINT), + ("wParam", WPARAM), + ("lParam", LPARAM), + ("time", DWORD), + ("pt", POINT)] tagMSG = MSG MAX_PATH = 260 -@ctypes.util.struct -class WIN32_FIND_DATAA: - dwFileAttributes: DWORD - ftCreationTime: FILETIME - ftLastAccessTime: FILETIME - ftLastWriteTime: FILETIME - nFileSizeHigh: DWORD - nFileSizeLow: DWORD - dwReserved0: DWORD - dwReserved1: DWORD - cFileName: CHAR * MAX_PATH - cAlternateFileName: CHAR * 14 - -@ctypes.util.struct -class WIN32_FIND_DATAW: - dwFileAttributes: DWORD - ftCreationTime: FILETIME - ftLastAccessTime: FILETIME - ftLastWriteTime: FILETIME - nFileSizeHigh: DWORD - nFileSizeLow: DWORD - dwReserved0: DWORD - dwReserved1: DWORD - cFileName: WCHAR * MAX_PATH - cAlternateFileName: WCHAR * 14 +class WIN32_FIND_DATAA(ctypes.Structure): + _fields_ = [("dwFileAttributes", DWORD), + ("ftCreationTime", FILETIME), + ("ftLastAccessTime", FILETIME), + ("ftLastWriteTime", FILETIME), + ("nFileSizeHigh", DWORD), + ("nFileSizeLow", DWORD), + ("dwReserved0", DWORD), + ("dwReserved1", DWORD), + ("cFileName", CHAR * MAX_PATH), + ("cAlternateFileName", CHAR * 14)] + +class WIN32_FIND_DATAW(ctypes.Structure): + _fields_ = [("dwFileAttributes", DWORD), + ("ftCreationTime", FILETIME), + ("ftLastAccessTime", FILETIME), + ("ftLastWriteTime", FILETIME), + ("nFileSizeHigh", DWORD), + ("nFileSizeLow", DWORD), + ("dwReserved0", DWORD), + ("dwReserved1", DWORD), + ("cFileName", WCHAR * MAX_PATH), + ("cAlternateFileName", WCHAR * 14)] ################################################################ # Pointer types diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 453dafe2709eb2..3213a475127343 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -37,7 +37,6 @@ try: import ctypes - import ctypes.util except ImportError: ctypes = None @@ -2850,11 +2849,8 @@ def test_memoryview_cast_1D_ND(self): if ctypes: # format: "T{>l:x:>d:y:}" - @ctypes.util.struct(endian='big') - class BEPoint: - x: ctypes.c_long - y: ctypes.c_double - + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] point = BEPoint(100, 200.1) m1 = memoryview(point) m2 = m1.cast('B') @@ -3254,11 +3250,8 @@ def test_memoryview_compare_special_cases(self): # Some ctypes format strings are unknown to the struct module. if ctypes: # format: "T{>l:x:>l:y:}" - @ctypes.util.struct(endian='big') - class BEPoint: - x: ctypes.c_long - y: ctypes.c_long - + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] point = BEPoint(100, 200) a = memoryview(point) b = memoryview(point) @@ -3995,11 +3988,8 @@ def test_memoryview_tobytes(self): # Unknown formats are handled: tobytes() purely depends on itemsize. if ctypes: # format: "T{>l:x:>l:y:}" - @ctypes.util.struct(endian='big') - class BEPoint: - x: ctypes.c_long - y: ctypes.c_long - + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] point = BEPoint(100, 200) a = memoryview(point) self.assertEqual(a.tobytes(), bytes(point)) From 2a68f158ae5a6c5746a768f091130feda5cfefb4 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 03:32:30 +0200 Subject: [PATCH 3/8] Fix test.support --- Lib/test/support/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 86972ea9c4e78f..e5bc2fd9b3d095 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -258,6 +258,7 @@ def GetProcessWindowStation() -> ctypes.wintypes.HANDLE: if not h: raise ctypes.WinError() + @ctypes.util.wrap_dll_function(user32) def GetUserObjectInformationW( hObj: ctypes.wintypes.HANDLE, nIndex: ctypes.c_int, @@ -271,9 +272,9 @@ def GetUserObjectInformationW( needed = ctypes.wintypes.DWORD() res = GetUserObjectInformationW(h, UOI_FLAGS, - uof, + ctypes.byref(uof), ctypes.sizeof(uof), - needed) + ctypes.byref(needed)) if not res: raise ctypes.WinError() if not bool(uof.dwFlags & WSF_VISIBLE): From a1a91c8fd00df35d32ad0b15a87c9218c0941e3c Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 03:35:47 +0200 Subject: [PATCH 4/8] Run prek: remove unused import --- Lib/test/test_codecs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py index bc6f9b31e06c80..31704955df3e14 100644 --- a/Lib/test/test_codecs.py +++ b/Lib/test/test_codecs.py @@ -39,7 +39,7 @@ def check(input, expect): # On small versions of Windows like Windows IoT or Windows Nano Server, # not all codepages are present def is_code_page_present(cp): - from ctypes import POINTER, WINFUNCTYPE, WinDLL, Structure + from ctypes import POINTER, WINFUNCTYPE, WinDLL from ctypes.util import struct from ctypes.wintypes import BOOL, BYTE, WCHAR, UINT, DWORD From ad1835f3d801368dd8f74be305e29e4fc95db7d2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 12:15:50 +0200 Subject: [PATCH 5/8] Patch _pyrepl and test_buffer --- Lib/_pyrepl/windows_console.py | 77 ++++++++++++++++------------------ Lib/test/test_buffer.py | 22 +++++++--- 2 files changed, 52 insertions(+), 47 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 3768a22ad16f7b..053682810d4a7a 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -23,7 +23,7 @@ import os import sys -import ctypes +import ctypes.util import types from dataclasses import dataclass from ctypes.wintypes import ( @@ -37,7 +37,7 @@ WCHAR, SHORT, ) -from ctypes import Structure, POINTER, Union +from ctypes import POINTER, Union from typing import TYPE_CHECKING from _colorize import ANSIColors @@ -723,61 +723,56 @@ def repaint(self) -> None: # Windows interop -class CONSOLE_SCREEN_BUFFER_INFO(Structure): - _fields_ = [ - ("dwSize", _COORD), - ("dwCursorPosition", _COORD), - ("wAttributes", WORD), - ("srWindow", SMALL_RECT), - ("dwMaximumWindowSize", _COORD), - ] +@ctypes.util.struct +class CONSOLE_SCREEN_BUFFER_INFO: + dwSize: _COORD + dwCursorPosition: _COORD + wAttributes: WORD + srWindow: SMALL_RECT + dwMaximumWindowSize: _COORD -class CONSOLE_CURSOR_INFO(Structure): - _fields_ = [ - ("dwSize", DWORD), - ("bVisible", BOOL), - ] +@ctypes.util.struct +class CONSOLE_CURSOR_INFO: + dwSize: DWORD + bVisible: BOOL -class CHAR_INFO(Structure): - _fields_ = [ - ("UnicodeChar", WCHAR), - ("Attributes", WORD), - ] +@ctypes.util.struct +class CHAR_INFO: + UnicodeChar: WCHAR + Attributes: WORD class Char(Union): - _fields_ = [ - ("UnicodeChar", WCHAR), - ("Char", CHAR), - ] + UnicodeChar: WCHAR + Char: CHAR -class KeyEvent(ctypes.Structure): - _fields_ = [ - ("bKeyDown", BOOL), - ("wRepeatCount", WORD), - ("wVirtualKeyCode", WORD), - ("wVirtualScanCode", WORD), - ("uChar", Char), - ("dwControlKeyState", DWORD), - ] +@ctypes.util.struct +class KeyEvent: + bKeyDown: BOOL + wRepeatCount: WORD + wVirtualKeyCode: WORD + wVirtualScanCode: WORD + uChar: Char + dwControlKeyState: DWORD -class WindowsBufferSizeEvent(ctypes.Structure): - _fields_ = [("dwSize", _COORD)] +@ctypes.util.struct +class WindowsBufferSizeEvent: + dwSize: _COORD class ConsoleEvent(ctypes.Union): - _fields_ = [ - ("KeyEvent", KeyEvent), - ("WindowsBufferSizeEvent", WindowsBufferSizeEvent), - ] + KeyEvent: KeyEvent + WindowsBufferSizeEvent: WindowsBufferSizeEvent -class INPUT_RECORD(Structure): - _fields_ = [("EventType", WORD), ("Event", ConsoleEvent)] +@ctypes.util.struct +class INPUT_RECORD: + EventType: WORD + Event: ConsoleEvent KEY_EVENT = 0x01 diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 3213a475127343..453dafe2709eb2 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -37,6 +37,7 @@ try: import ctypes + import ctypes.util except ImportError: ctypes = None @@ -2849,8 +2850,11 @@ def test_memoryview_cast_1D_ND(self): if ctypes: # format: "T{>l:x:>d:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_double + point = BEPoint(100, 200.1) m1 = memoryview(point) m2 = m1.cast('B') @@ -3250,8 +3254,11 @@ def test_memoryview_compare_special_cases(self): # Some ctypes format strings are unknown to the struct module. if ctypes: # format: "T{>l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_long + point = BEPoint(100, 200) a = memoryview(point) b = memoryview(point) @@ -3988,8 +3995,11 @@ def test_memoryview_tobytes(self): # Unknown formats are handled: tobytes() purely depends on itemsize. if ctypes: # format: "T{>l:x:>l:y:}" - class BEPoint(ctypes.BigEndianStructure): - _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + @ctypes.util.struct(endian='big') + class BEPoint: + x: ctypes.c_long + y: ctypes.c_long + point = BEPoint(100, 200) a = memoryview(point) self.assertEqual(a.tobytes(), bytes(point)) From 2e132343e82a6691bde4868be616ae18457fc92e Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 12:44:43 +0200 Subject: [PATCH 6/8] Use @ctypes.util.struct in more places --- Lib/test/test_ctypes/test_refcounts.py | 6 ++++-- Lib/test/test_ctypes/test_unicode.py | 8 +++++--- .../test_ctypes/test_win32_com_foreign_func.py | 15 +++++++-------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_ctypes/test_refcounts.py b/Lib/test/test_ctypes/test_refcounts.py index 1815649ceb5fff..81cc229391c466 100644 --- a/Lib/test/test_ctypes/test_refcounts.py +++ b/Lib/test/test_ctypes/test_refcounts.py @@ -54,8 +54,10 @@ def func(*args): gc.collect() self.assertEqual(sys.getrefcount(func), orig_refcount) - class X(ctypes.Structure): - _fields_ = [("a", OtherCallback)] + @ctypes.util.struct + class X: + a: OtherCallback + x = X() x.a = OtherCallback(func) diff --git a/Lib/test/test_ctypes/test_unicode.py b/Lib/test/test_ctypes/test_unicode.py index d9e17371d13572..8e91f8f2c56680 100644 --- a/Lib/test/test_ctypes/test_unicode.py +++ b/Lib/test/test_ctypes/test_unicode.py @@ -1,4 +1,4 @@ -import ctypes +import ctypes.util import unittest from test.support import import_helper _ctypes_test = import_helper.import_module("_ctypes_test") @@ -26,8 +26,10 @@ def test_buffers(self): self.assertEqual(buf[6:5:-1], "") def test_embedded_null(self): - class TestStruct(ctypes.Structure): - _fields_ = [("unicode", ctypes.c_wchar_p)] + @ctypes.util.struct + class TestStruct: + unicode: ctypes.c_wchar_p + t = TestStruct() # This would raise a ValueError: t.unicode = "foo\0bar\0\0" diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py index 7e54f8f6c31d33..c797ab98cf1e42 100644 --- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py +++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py @@ -1,4 +1,4 @@ -import ctypes +import ctypes.util import gc import sys import unittest @@ -20,14 +20,13 @@ E_NOINTERFACE = -2147467262 -class GUID(ctypes.Structure): +@ctypes.util.struct +class GUID: # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid - _fields_ = [ - ("Data1", DWORD), - ("Data2", WORD), - ("Data3", WORD), - ("Data4", BYTE * 8), - ] + Data1: DWORD + Data2: WORD + Data3: WORD + Data4: BYTE * 8 def create_proto_com_method(name, index, restype, *argtypes): From 4e523880bf1df4b096eaab4c62bf6ec20e3ad0bf Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 13:21:58 +0200 Subject: [PATCH 7/8] Fix _pyrepl unions --- Lib/_pyrepl/windows_console.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index 053682810d4a7a..e8c63ece679f0f 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -745,8 +745,10 @@ class CHAR_INFO: class Char(Union): - UnicodeChar: WCHAR - Char: CHAR + _fields_ = [ + ("UnicodeChar", WCHAR), + ("Char", CHAR), + ] @ctypes.util.struct @@ -765,8 +767,10 @@ class WindowsBufferSizeEvent: class ConsoleEvent(ctypes.Union): - KeyEvent: KeyEvent - WindowsBufferSizeEvent: WindowsBufferSizeEvent + _fields_ = [ + ("KeyEvent", KeyEvent), + ("WindowsBufferSizeEvent", WindowsBufferSizeEvent), + ] @ctypes.util.struct From 34355c05060523506d530d1d8131eeb19c935ca2 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 19 Jul 2026 13:37:19 +0200 Subject: [PATCH 8/8] Leave _pyrepl unchanged _pyrepl is tested by mypy which doesn't know yet about @ctypes.util.struct. --- Lib/_pyrepl/windows_console.py | 65 +++++++++++++++++----------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/Lib/_pyrepl/windows_console.py b/Lib/_pyrepl/windows_console.py index e8c63ece679f0f..3768a22ad16f7b 100644 --- a/Lib/_pyrepl/windows_console.py +++ b/Lib/_pyrepl/windows_console.py @@ -23,7 +23,7 @@ import os import sys -import ctypes.util +import ctypes import types from dataclasses import dataclass from ctypes.wintypes import ( @@ -37,7 +37,7 @@ WCHAR, SHORT, ) -from ctypes import POINTER, Union +from ctypes import Structure, POINTER, Union from typing import TYPE_CHECKING from _colorize import ANSIColors @@ -723,25 +723,28 @@ def repaint(self) -> None: # Windows interop -@ctypes.util.struct -class CONSOLE_SCREEN_BUFFER_INFO: - dwSize: _COORD - dwCursorPosition: _COORD - wAttributes: WORD - srWindow: SMALL_RECT - dwMaximumWindowSize: _COORD +class CONSOLE_SCREEN_BUFFER_INFO(Structure): + _fields_ = [ + ("dwSize", _COORD), + ("dwCursorPosition", _COORD), + ("wAttributes", WORD), + ("srWindow", SMALL_RECT), + ("dwMaximumWindowSize", _COORD), + ] -@ctypes.util.struct -class CONSOLE_CURSOR_INFO: - dwSize: DWORD - bVisible: BOOL +class CONSOLE_CURSOR_INFO(Structure): + _fields_ = [ + ("dwSize", DWORD), + ("bVisible", BOOL), + ] -@ctypes.util.struct -class CHAR_INFO: - UnicodeChar: WCHAR - Attributes: WORD +class CHAR_INFO(Structure): + _fields_ = [ + ("UnicodeChar", WCHAR), + ("Attributes", WORD), + ] class Char(Union): @@ -751,19 +754,19 @@ class Char(Union): ] -@ctypes.util.struct -class KeyEvent: - bKeyDown: BOOL - wRepeatCount: WORD - wVirtualKeyCode: WORD - wVirtualScanCode: WORD - uChar: Char - dwControlKeyState: DWORD +class KeyEvent(ctypes.Structure): + _fields_ = [ + ("bKeyDown", BOOL), + ("wRepeatCount", WORD), + ("wVirtualKeyCode", WORD), + ("wVirtualScanCode", WORD), + ("uChar", Char), + ("dwControlKeyState", DWORD), + ] -@ctypes.util.struct -class WindowsBufferSizeEvent: - dwSize: _COORD +class WindowsBufferSizeEvent(ctypes.Structure): + _fields_ = [("dwSize", _COORD)] class ConsoleEvent(ctypes.Union): @@ -773,10 +776,8 @@ class ConsoleEvent(ctypes.Union): ] -@ctypes.util.struct -class INPUT_RECORD: - EventType: WORD - Event: ConsoleEvent +class INPUT_RECORD(Structure): + _fields_ = [("EventType", WORD), ("Event", ConsoleEvent)] KEY_EVENT = 0x01