Skip to content

Commit b5230d7

Browse files
miss-islingtonaiskencukou
authored
[3.14] pythongh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on Windows (pythonGH-136878) (python#138546)
pythongh-83424: Allow empty name if handle is non-null when create ctypes.CDLL on Windows (pythonGH-136878) (cherry picked from commit ed522ed) Co-authored-by: AN Long <[email protected]> Co-authored-by: Petr Viktorin <[email protected]>
1 parent d729fcf commit b5230d7

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 = {}
@@ -416,52 +416,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
416416
use_errno=False,
417417
use_last_error=False,
418418
winmode=None):
419+
class _FuncPtr(_CFuncPtr):
420+
_flags_ = self._func_flags_
421+
_restype_ = self._func_restype_
422+
if use_errno:
423+
_flags_ |= _FUNCFLAG_USE_ERRNO
424+
if use_last_error:
425+
_flags_ |= _FUNCFLAG_USE_LASTERROR
426+
427+
self._FuncPtr = _FuncPtr
419428
if name:
420429
name = _os.fspath(name)
421430

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

466473
def __repr__(self):
467474
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)