From: Victor Stinner Date: Sun, 19 Jul 2026 15:12:07 +0000 (+0200) Subject: gh-153903: Use `@ctypes.util.wrap_dll_function()` (#154122) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=249d5e0a10839f9891caa85de35a8a9cf5d7a3a3;p=thirdparty%2FPython%2Fcpython.git gh-153903: Use `@ctypes.util.wrap_dll_function()` (#154122) --- diff --git a/Lib/platform.py b/Lib/platform.py index 36489d4fdd98..3141998e02df 100644 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -543,21 +543,25 @@ def android_ver(release="", api_level=0, manufacturer="", model="", device="", is_emulator=False): if sys.platform == "android": try: - from ctypes import CDLL, c_char_p, create_string_buffer + from ctypes import CDLL, c_int, c_char_p, create_string_buffer + from ctypes.util import wrap_dll_function except ImportError: pass else: # An NDK developer confirmed that this is an officially-supported # API (https://stackoverflow.com/a/28416743). Use `getattr` to avoid # private name mangling. - system_property_get = getattr(CDLL("libc.so"), "__system_property_get") - system_property_get.argtypes = (c_char_p, c_char_p) + libc = CDLL("libc.so") + + @wrap_dll_function(libc) + def __system_property_get(name: c_char_p, value: c_char_p) -> c_int: + pass def getprop(name, default): # https://android.googlesource.com/platform/bionic/+/refs/tags/android-5.0.0_r1/libc/include/sys/system_properties.h#39 PROP_VALUE_MAX = 92 buffer = create_string_buffer(PROP_VALUE_MAX) - length = system_property_get(name.encode("UTF-8"), buffer) + length = __system_property_get(name.encode("UTF-8"), buffer) if length == 0: # This API doesn’t distinguish between an empty property and # a missing one. diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index 3227b91bd82a..13a3199b1f12 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -995,7 +995,7 @@ def collect_windows(info_add): # windows.RtlAreLongPathsEnabled: RtlAreLongPathsEnabled() # windows.is_admin: IsUserAnAdmin() try: - import ctypes + import ctypes.util if not hasattr(ctypes, 'WinDLL'): raise ImportError except ImportError: @@ -1004,20 +1004,19 @@ def collect_windows(info_add): ntdll = ctypes.WinDLL('ntdll') BOOLEAN = ctypes.c_ubyte try: - RtlAreLongPathsEnabled = ntdll.RtlAreLongPathsEnabled + @ctypes.util.wrap_dll_function(ntdll) + def RtlAreLongPathsEnabled() -> BOOLEAN: + pass except AttributeError: res = '' else: - RtlAreLongPathsEnabled.restype = BOOLEAN - RtlAreLongPathsEnabled.argtypes = () res = bool(RtlAreLongPathsEnabled()) info_add('windows.RtlAreLongPathsEnabled', res) - shell32 = ctypes.windll.shell32 - IsUserAnAdmin = shell32.IsUserAnAdmin - IsUserAnAdmin.restype = BOOLEAN - IsUserAnAdmin.argtypes = () - info_add('windows.is_admin', IsUserAnAdmin()) + @ctypes.util.wrap_dll_function(ctypes.windll.shell32) + def IsUserAnAdmin() -> BOOLEAN: + pass + info_add('windows.is_admin', bool(IsUserAnAdmin())) try: import _winapi diff --git a/Lib/test/test_android.py b/Lib/test/test_android.py index 31daafbc3d63..201d701aff11 100644 --- a/Lib/test/test_android.py +++ b/Lib/test/test_android.py @@ -41,13 +41,18 @@ class TestAndroidOutput(unittest.TestCase): try: from ctypes import CDLL, c_char_p, c_int - android_log_write = getattr(CDLL("liblog.so"), "__android_log_write") - android_log_write.argtypes = (c_int, c_char_p, c_char_p) - ANDROID_LOG_INFO = 4 + from ctypes.util import wrap_dll_function + liblog = CDLL("liblog.so") + + @wrap_dll_function(liblog) + def __android_log_write(prio: c_int, tag: c_char_p, + text: c_char_p) -> c_int: + pass # Separate tests using a marker line with a different tag. + ANDROID_LOG_INFO = 4 tag, message = "python.test", f"{self.id()} {time()}" - android_log_write( + __android_log_write( ANDROID_LOG_INFO, tag.encode("UTF-8"), message.encode("UTF-8")) self.assert_log("I", tag, message, skip=True) except: diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index 40036609a190..1ff600e30bf4 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -1884,19 +1884,31 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): # The global path configuration (_Py_path_config) must be a copy # of the path configuration of PyInterpreter.config (PyConfig). ctypes = import_helper.import_module('ctypes') + import ctypes.util # noqa: F811 - def get_func(name): - func = getattr(ctypes.pythonapi, name) - func.argtypes = () - func.restype = ctypes.c_wchar_p - return func - - Py_GetPath = get_func('Py_GetPath') - Py_GetPrefix = get_func('Py_GetPrefix') - Py_GetExecPrefix = get_func('Py_GetExecPrefix') - Py_GetProgramName = get_func('Py_GetProgramName') - Py_GetProgramFullPath = get_func('Py_GetProgramFullPath') - Py_GetPythonHome = get_func('Py_GetPythonHome') + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetPath() -> ctypes.c_wchar_p: + pass + + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetPrefix() -> ctypes.c_wchar_p: + pass + + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetExecPrefix() -> ctypes.c_wchar_p: + pass + + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetProgramName() -> ctypes.c_wchar_p: + pass + + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetProgramFullPath() -> ctypes.c_wchar_p: + pass + + @ctypes.util.wrap_dll_function(ctypes.pythonapi) + def Py_GetPythonHome() -> ctypes.c_wchar_p: + pass config = _testinternalcapi.get_configs()['config'] diff --git a/Lib/test/test_ntpath.py b/Lib/test/test_ntpath.py index a3728b58335e..936332bf94ff 100644 --- a/Lib/test/test_ntpath.py +++ b/Lib/test/test_ntpath.py @@ -31,19 +31,29 @@ else: HAVE_GETFINALPATHNAME = True try: - import ctypes + import ctypes.util + import ctypes.wintypes except ImportError: HAVE_GETSHORTPATHNAME = False else: HAVE_GETSHORTPATHNAME = True def _getshortpathname(path): - GSPN = ctypes.WinDLL("kernel32", use_last_error=True).GetShortPathNameW - GSPN.argtypes = [ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32] - GSPN.restype = ctypes.c_uint32 + kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + + @ctypes.util.wrap_dll_function(kernel32) + def GetShortPathNameW( + lpszLongPath: ctypes.c_wchar_p, + lpszShortPath: ctypes.c_wchar_p, + cchBuffer: ctypes.wintypes.DWORD, + ) -> ctypes.wintypes.DWORD: + pass + GSPN = GetShortPathNameW + result_len = GSPN(path, None, 0) if not result_len: raise OSError("failed to get short path name 0x{:08X}" .format(ctypes.get_last_error())) + result = ctypes.create_unicode_buffer(result_len) result_len = GSPN(path, result, result_len) return result[:result_len] diff --git a/Lib/test/test_os/test_windows.py b/Lib/test/test_os/test_windows.py index f1c6283f60d3..b21dd8a4dca6 100644 --- a/Lib/test/test_os/test_windows.py +++ b/Lib/test/test_os/test_windows.py @@ -27,7 +27,7 @@ class Win32KillTests(unittest.TestCase): # subprocess to the parent that the interpreter is ready. When it # becomes ready, send *sig* via os.kill to the subprocess and check # that the return code is equal to *sig*. - import ctypes + import ctypes.util from ctypes import wintypes import msvcrt @@ -35,14 +35,17 @@ class Win32KillTests(unittest.TestCase): # process has exited, use PeekNamedPipe to see what's inside stdout # without waiting. This is done so we can tell that the interpreter # is started and running at a point where it could handle a signal. - PeekNamedPipe = ctypes.windll.kernel32.PeekNamedPipe - PeekNamedPipe.restype = wintypes.BOOL - PeekNamedPipe.argtypes = (wintypes.HANDLE, # Pipe handle - ctypes.POINTER(ctypes.c_char), # stdout buf - wintypes.DWORD, # Buffer size - ctypes.POINTER(wintypes.DWORD), # bytes read - ctypes.POINTER(wintypes.DWORD), # bytes avail - ctypes.POINTER(wintypes.DWORD)) # bytes left + @ctypes.util.wrap_dll_function(ctypes.windll.kernel32) + def PeekNamedPipe( + hNamedPipe: wintypes.HANDLE, + lpBuffer: ctypes.POINTER(ctypes.c_char), + nBufferSize: wintypes.DWORD, + lpBytesRead: ctypes.POINTER(wintypes.DWORD), + lpTotalBytesAvail: ctypes.POINTER(wintypes.DWORD), + lpBytesLeftThisMessage: ctypes.POINTER(wintypes.DWORD), + ) -> wintypes.BOOL: + pass + msg = "running" proc = subprocess.Popen([sys.executable, "-c", "import sys;" @@ -126,10 +129,11 @@ class Win32KillTests(unittest.TestCase): # Make a NULL value by creating a pointer with no argument. NULL = ctypes.POINTER(ctypes.c_int)() - SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler - SetConsoleCtrlHandler.argtypes = (ctypes.POINTER(ctypes.c_int), - wintypes.BOOL) - SetConsoleCtrlHandler.restype = wintypes.BOOL + + @ctypes.util.wrap_dll_function(ctypes.windll.kernel32) + def SetConsoleCtrlHandler(HandlerRoutine: ctypes.POINTER(ctypes.c_int), + Add: wintypes.BOOL) -> wintypes.BOOL: + pass # Calling this with NULL and FALSE causes the calling process to # handle Ctrl+C, rather than ignore it. This property is inherited @@ -458,17 +462,20 @@ class Win32NtTests(unittest.TestCase): import ctypes.wintypes # noqa: F811 kernel = ctypes.WinDLL('Kernel32.dll', use_last_error=True) - kernel.GetCurrentProcess.restype = ctypes.wintypes.HANDLE + @ctypes.util.wrap_dll_function(kernel) + def GetCurrentProcess() -> ctypes.wintypes.HANDLE: + pass - kernel.GetProcessHandleCount.restype = ctypes.wintypes.BOOL - kernel.GetProcessHandleCount.argtypes = (ctypes.wintypes.HANDLE, - ctypes.wintypes.LPDWORD) + @ctypes.util.wrap_dll_function(kernel) + def GetProcessHandleCount(khProcess: ctypes.wintypes.HANDLE, + pdwHandleCount: ctypes.wintypes.LPDWORD) -> ctypes.wintypes.BOOL: + pass # This is a pseudo-handle that doesn't need to be closed - hproc = kernel.GetCurrentProcess() + hproc = GetCurrentProcess() handle_count = ctypes.wintypes.DWORD() - ok = kernel.GetProcessHandleCount(hproc, ctypes.byref(handle_count)) + ok = GetProcessHandleCount(hproc, ctypes.byref(handle_count)) self.assertEqual(1, ok) before_count = handle_count.value diff --git a/Lib/test/win_console_handler.py b/Lib/test/win_console_handler.py index e7779b936350..a1c7c42c1093 100644 --- a/Lib/test/win_console_handler.py +++ b/Lib/test/win_console_handler.py @@ -15,7 +15,7 @@ import mmap import sys # Function prototype for the handler function. Returns BOOL, takes a DWORD. -HandlerRoutine = WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD) +HANDLER_ROUTINE = WINFUNCTYPE(wintypes.BOOL, wintypes.DWORD) def _ctrl_handler(sig): """Handle a sig event and return 0 to terminate the process""" @@ -27,12 +27,13 @@ def _ctrl_handler(sig): print("UNKNOWN EVENT") return 0 -ctrl_handler = HandlerRoutine(_ctrl_handler) +ctrl_handler = HANDLER_ROUTINE(_ctrl_handler) -SetConsoleCtrlHandler = ctypes.windll.kernel32.SetConsoleCtrlHandler -SetConsoleCtrlHandler.argtypes = (HandlerRoutine, wintypes.BOOL) -SetConsoleCtrlHandler.restype = wintypes.BOOL +@ctypes.util.wrap_dll_function(ctypes.windll.kernel32) +def SetConsoleCtrlHandler(HandlerRoutine: HANDLER_ROUTINE, + Add: wintypes.BOOL) -> wintypes.BOOL: + pass if __name__ == "__main__": # Add our console control handling function with value 1