gh-153903: Use `@ctypes.util.wrap_dll_function()` (#154122)

vstinner <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/249d5e0a10839f9891caa85de35a8a9cf5d7a3a3
commit: 249d5e0a10839f9891caa85de35a8a9cf5d7a3a3
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-07-19T15:12:07Z
summary:

gh-153903: Use `@ctypes.util.wrap_dll_function()` (#154122)

files:
M Lib/platform.py
M Lib/test/pythoninfo.py
M Lib/test/test_android.py
M Lib/test/test_embed.py
M Lib/test/test_ntpath.py
M Lib/test/test_os/test_windows.py
M Lib/test/win_console_handler.py

diff --git a/Lib/platform.py b/Lib/platform.py
index 36489d4fdd98ae2..3141998e02dfe21 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 3227b91bd82a863..13a3199b1f1267b 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 = '<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
diff --git a/Lib/test/test_android.py b/Lib/test/test_android.py
index 31daafbc3d63009..201d701aff11eb2 100644
--- a/Lib/test/test_android.py
+++ b/Lib/test/test_android.py
@@ -41,13 +41,18 @@ def logcat_thread():
 
         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 40036609a190551..1ff600e30bf4cbd 100644
--- a/Lib/test/test_embed.py
+++ b/Lib/test/test_embed.py
@@ -1884,19 +1884,31 @@ def test_global_pathconfig(self):
         # 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 a3728b58335e63b..936332bf94ffe77 100644
--- a/Lib/test/test_ntpath.py
+++ b/Lib/test/test_ntpath.py
@@ -31,19 +31,29 @@
     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 f1c6283f60d35ed..b21dd8a4dca6609 100644
--- a/Lib/test/test_os/test_windows.py
+++ b/Lib/test/test_os/test_windows.py
@@ -27,7 +27,7 @@ def _kill(self, sig):
         # 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 @@ def _kill(self, sig):
         # 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 @@ def test_CTRL_C_EVENT(self):
 
         # 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 @@ def test_getfinalpathname_handles(self):
         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 e7779b9363503b4..a1c7c42c109330b 100644
--- a/Lib/test/win_console_handler.py
+++ b/Lib/test/win_console_handler.py
@@ -15,7 +15,7 @@
 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
[email protected]_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

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.