]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153903: Use `@ctypes.util.wrap_dll_function()` (#154122)
authorVictor Stinner <vstinner@python.org>
Sun, 19 Jul 2026 15:12:07 +0000 (17:12 +0200)
committerGitHub <noreply@github.com>
Sun, 19 Jul 2026 15:12:07 +0000 (15:12 +0000)
Lib/platform.py
Lib/test/pythoninfo.py
Lib/test/test_android.py
Lib/test/test_embed.py
Lib/test/test_ntpath.py
Lib/test/test_os/test_windows.py
Lib/test/win_console_handler.py

index 36489d4fdd98ae2bd3a6c2760b98ff3e1e014882..3141998e02dfe21cbe5b15a93bf3e8afce399b4c 100644 (file)
@@ -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.
index 3227b91bd82a86327dc380162c8b4184fdf79cf5..13a3199b1f1267bd54a6a21db4cea7cc6035113f 100644 (file)
@@ -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 = '<function not available>'
         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
index 31daafbc3d630093a2c3c336cf4ea75af08c9df7..201d701aff11eb2dada22f13cc902c9aecfe735c 100644 (file)
@@ -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:
index 40036609a1905510cd3a47b5abfba52561ae0430..1ff600e30bf4cbd147e48bfca2301da44ddacbb2 100644 (file)
@@ -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']
 
index a3728b58335e63b70ec5c15dfad3dda05798878f..936332bf94ffe77486e4f2a5d74a2e927a2cf911 100644 (file)
@@ -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]
index f1c6283f60d35ed897e49eb8611dda7fd34029ca..b21dd8a4dca6609c3c845e1068b30c23b2f52db8 100644 (file)
@@ -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
index e7779b9363503b452116ea70181e7adfe7651b8f..a1c7c42c109330bfa3a309d4c14f853667a2f53a 100644 (file)
@@ -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