]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.14] gh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 8 Oct 2025 13:56:13 +0000 (15:56 +0200)
committerGitHub <noreply@github.com>
Wed, 8 Oct 2025 13:56:13 +0000 (15:56 +0200)
gh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on Windows (GH-136878)
(cherry picked from commit ed522ed211b7b9ea9d85a93b5d6ca79b2df3ef58)

Co-authored-by: AN Long <aisk@users.noreply.github.com>
Co-authored-by: Petr Viktorin <encukou@gmail.com>
Lib/ctypes/__init__.py
Lib/test/test_ctypes/test_loading.py
Misc/NEWS.d/next/Library/2025-07-21-01-16-32.gh-issue-83424.Y3tEV4.rst [new file with mode: 0644]

index 823a3692fd1bbf9cdc0b60f025f3707132d59c3f..a41f4a16a5c6eeb1f7fcb9db88263bd7483cab7b 100644 (file)
@@ -108,7 +108,7 @@ def CFUNCTYPE(restype, *argtypes, **kw):
     return CFunctionType
 
 if _os.name == "nt":
-    from _ctypes import LoadLibrary as _dlopen
+    from _ctypes import LoadLibrary as _LoadLibrary
     from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
 
     _win_functype_cache = {}
@@ -416,52 +416,59 @@ class CDLL(object):
                  use_errno=False,
                  use_last_error=False,
                  winmode=None):
+        class _FuncPtr(_CFuncPtr):
+            _flags_ = self._func_flags_
+            _restype_ = self._func_restype_
+            if use_errno:
+                _flags_ |= _FUNCFLAG_USE_ERRNO
+            if use_last_error:
+                _flags_ |= _FUNCFLAG_USE_LASTERROR
+
+        self._FuncPtr = _FuncPtr
         if name:
             name = _os.fspath(name)
 
+        self._handle = self._load_library(name, mode, handle, winmode)
+
+    if _os.name == "nt":
+        def _load_library(self, name, mode, handle, winmode):
+            if winmode is None:
+                import nt as _nt
+                winmode = _nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
+                # WINAPI LoadLibrary searches for a DLL if the given name
+                # is not fully qualified with an explicit drive. For POSIX
+                # compatibility, and because the DLL search path no longer
+                # contains the working directory, begin by fully resolving
+                # any name that contains a path separator.
+                if name is not None and ('/' in name or '\\' in name):
+                    name = _nt._getfullpathname(name)
+                    winmode |= _nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
+            self._name = name
+            if handle is not None:
+                return handle
+            return _LoadLibrary(self._name, winmode)
+
+    else:
+        def _load_library(self, name, mode, handle, winmode):
             # If the filename that has been provided is an iOS/tvOS/watchOS
             # .fwork file, dereference the location to the true origin of the
             # binary.
-            if name.endswith(".fwork"):
+            if name and name.endswith(".fwork"):
                 with open(name) as f:
                     name = _os.path.join(
                         _os.path.dirname(_sys.executable),
                         f.read().strip()
                     )
-
-        self._name = name
-        flags = self._func_flags_
-        if use_errno:
-            flags |= _FUNCFLAG_USE_ERRNO
-        if use_last_error:
-            flags |= _FUNCFLAG_USE_LASTERROR
-        if _sys.platform.startswith("aix"):
-            """When the name contains ".a(" and ends with ")",
-               e.g., "libFOO.a(libFOO.so)" - this is taken to be an
-               archive(member) syntax for dlopen(), and the mode is adjusted.
-               Otherwise, name is presented to dlopen() as a file argument.
-            """
-            if name and name.endswith(")") and ".a(" in name:
-                mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
-        if _os.name == "nt":
-            if winmode is not None:
-                mode = winmode
-            else:
-                import nt
-                mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
-                if '/' in name or '\\' in name:
-                    self._name = nt._getfullpathname(self._name)
-                    mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
-
-        class _FuncPtr(_CFuncPtr):
-            _flags_ = flags
-            _restype_ = self._func_restype_
-        self._FuncPtr = _FuncPtr
-
-        if handle is None:
-            self._handle = _dlopen(self._name, mode)
-        else:
-            self._handle = handle
+            if _sys.platform.startswith("aix"):
+                """When the name contains ".a(" and ends with ")",
+                   e.g., "libFOO.a(libFOO.so)" - this is taken to be an
+                   archive(member) syntax for dlopen(), and the mode is adjusted.
+                   Otherwise, name is presented to dlopen() as a file argument.
+                """
+                if name and name.endswith(")") and ".a(" in name:
+                    mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
+            self._name = name
+            return _dlopen(name, mode)
 
     def __repr__(self):
         return "<%s '%s', handle %x at %#x>" % \
index 13ed813ad98c311ee4d0ce0a4c2342fbde5ed471..3b8332fbb30928fdcfca5eefc80aa69950fa13d9 100644 (file)
@@ -100,6 +100,12 @@ class LoaderTest(unittest.TestCase):
 
         self.assertRaises(AttributeError, dll.__getitem__, 1234)
 
+    @unittest.skipUnless(os.name == "nt", 'Windows-specific test')
+    def test_load_without_name_and_with_handle(self):
+        handle = ctypes.windll.kernel32._handle
+        lib = ctypes.WinDLL(name=None, handle=handle)
+        self.assertIs(handle, lib._handle)
+
     @unittest.skipUnless(os.name == "nt", 'Windows-specific test')
     def test_1703286_A(self):
         # On winXP 64-bit, advapi32 loads at an address that does
diff --git a/Misc/NEWS.d/next/Library/2025-07-21-01-16-32.gh-issue-83424.Y3tEV4.rst b/Misc/NEWS.d/next/Library/2025-07-21-01-16-32.gh-issue-83424.Y3tEV4.rst
new file mode 100644 (file)
index 0000000..0c1a16c
--- /dev/null
@@ -0,0 +1,2 @@
+Allows creating a :class:`ctypes.CDLL` without name when passing a handle as
+an argument.