Skip to content

Commit ed522ed

Browse files
authored
gh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on Windows (GH-136878)
1 parent 9158bcf commit ed522ed

File tree

3 files changed

+51
-36
lines changed

3 files changed

+51
-36
lines changed

Lib/ctypes/__init__.py

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ class CFunctionType(_CFuncPtr):
108108
return CFunctionType
109109

110110
if _os.name == "nt":
111-
from _ctypes import LoadLibrary as _dlopen
111+
from _ctypes import LoadLibrary as _LoadLibrary
112112
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
113113

114114
_win_functype_cache = {}
@@ -410,52 +410,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
410410
use_errno=False,
411411
use_last_error=False,
412412
winmode=None):
413+
class _FuncPtr(_CFuncPtr):
414+
_flags_ = self._func_flags_
415+
_restype_ = self._func_restype_
416+
if use_errno:
417+
_flags_ |= _FUNCFLAG_USE_ERRNO
418+
if use_last_error:
419+
_flags_ |= _FUNCFLAG_USE_LASTERROR
420+
421+
self._FuncPtr = _FuncPtr
413422
if name:
414423
name = _os.fspath(name)
415424

425+
self._handle = self._load_library(name, mode, handle, winmode)
426+
427+
if _os.name == "nt":
428+
def _load_library(self, name, mode, handle, winmode):
429+
if winmode is None:
430+
import nt as _nt
431+
winmode = _nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
432+
# WINAPI LoadLibrary searches for a DLL if the given name
433+
# is not fully qualified with an explicit drive. For POSIX
434+
# compatibility, and because the DLL search path no longer
435+
# contains the working directory, begin by fully resolving
436+
# any name that contains a path separator.
437+
if name is not None and ('/' in name or '\\' in name):
438+
name = _nt._getfullpathname(name)
439+
winmode |= _nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
440+
self._name = name
441+
if handle is not None:
442+
return handle
443+
return _LoadLibrary(self._name, winmode)
444+
445+
else:
446+
def _load_library(self, name, mode, handle, winmode):
416447
# If the filename that has been provided is an iOS/tvOS/watchOS
417448
# .fwork file, dereference the location to the true origin of the
418449
# binary.
419-
if name.endswith(".fwork"):
450+
if name and name.endswith(".fwork"):
420451
with open(name) as f:
421452
name = _os.path.join(
422453
_os.path.dirname(_sys.executable),
423454
f.read().strip()
424455
)
425-
426-
self._name = name
427-
flags = self._func_flags_
428-
if use_errno:
429-
flags |= _FUNCFLAG_USE_ERRNO
430-
if use_last_error:
431-
flags |= _FUNCFLAG_USE_LASTERROR
432-
if _sys.platform.startswith("aix"):
433-
"""When the name contains ".a(" and ends with ")",
434-
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
435-
archive(member) syntax for dlopen(), and the mode is adjusted.
436-
Otherwise, name is presented to dlopen() as a file argument.
437-
"""
438-
if name and name.endswith(")") and ".a(" in name:
439-
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
440-
if _os.name == "nt":
441-
if winmode is not None:
442-
mode = winmode
443-
else:
444-
import nt
445-
mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
446-
if '/' in name or '\\' in name:
447-
self._name = nt._getfullpathname(self._name)
448-
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
449-
450-
class _FuncPtr(_CFuncPtr):
451-
_flags_ = flags
452-
_restype_ = self._func_restype_
453-
self._FuncPtr = _FuncPtr
454-
455-
if handle is None:
456-
self._handle = _dlopen(self._name, mode)
457-
else:
458-
self._handle = handle
456+
if _sys.platform.startswith("aix"):
457+
"""When the name contains ".a(" and ends with ")",
458+
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
459+
archive(member) syntax for dlopen(), and the mode is adjusted.
460+
Otherwise, name is presented to dlopen() as a file argument.
461+
"""
462+
if name and name.endswith(")") and ".a(" in name:
463+
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
464+
self._name = name
465+
return _dlopen(name, mode)
459466

460467
def __repr__(self):
461468
return "<%s '%s', handle %x at %#x>" % \

Lib/test/test_ctypes/test_loading.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ def test_load_ordinal_functions(self):
100100

101101
self.assertRaises(AttributeError, dll.__getitem__, 1234)
102102

103+
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
104+
def test_load_without_name_and_with_handle(self):
105+
handle = ctypes.windll.kernel32._handle
106+
lib = ctypes.WinDLL(name=None, handle=handle)
107+
self.assertIs(handle, lib._handle)
108+
103109
@unittest.skipUnless(os.name == "nt", 'Windows-specific test')
104110
def test_1703286_A(self):
105111
# On winXP 64-bit, advapi32 loads at an address that does
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allows creating a :class:`ctypes.CDLL` without name when passing a handle as
2+
an argument.

0 commit comments

Comments
 (0)