https://github.com/python/cpython/commit/ed522ed211b7b9ea9d85a93b5d6ca79b2df3ef58
commit: ed522ed211b7b9ea9d85a93b5d6ca79b2df3ef58
branch: main
author: AN Long <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2025-09-05T18:51:35+03:00
summary:
gh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on
Windows (GH-136878)
files:
A Misc/NEWS.d/next/Library/2025-07-21-01-16-32.gh-issue-83424.Y3tEV4.rst
M Lib/ctypes/__init__.py
M Lib/test/test_ctypes/test_loading.py
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index d6d07a13f756e2..768cbada894613 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -108,7 +108,7 @@ class CFunctionType(_CFuncPtr):
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 = {}
@@ -410,52 +410,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
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>" % \
diff --git a/Lib/test/test_ctypes/test_loading.py
b/Lib/test/test_ctypes/test_loading.py
index 13ed813ad98c31..3b8332fbb30928 100644
--- a/Lib/test/test_ctypes/test_loading.py
+++ b/Lib/test/test_ctypes/test_loading.py
@@ -100,6 +100,12 @@ def test_load_ordinal_functions(self):
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
index 00000000000000..0c1a16cdb29f43
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-07-21-01-16-32.gh-issue-83424.Y3tEV4.rst
@@ -0,0 +1,2 @@
+Allows creating a :class:`ctypes.CDLL` without name when passing a handle as
+an argument.
_______________________________________________
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]