Skip to content

Commit ec05a63

Browse files
aiskmiss-islington
authored andcommitted
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]>
1 parent 5f61a0a commit ec05a63

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
@@ -107,7 +107,7 @@ class CFunctionType(_CFuncPtr):
107107
return CFunctionType
108108

109109
if _os.name == "nt":
110-
from _ctypes import LoadLibrary as _dlopen
110+
from _ctypes import LoadLibrary as _LoadLibrary
111111
from _ctypes import FUNCFLAG_STDCALL as _FUNCFLAG_STDCALL
112112

113113
_win_functype_cache = {}
@@ -344,52 +344,59 @@ def __init__(self, name, mode=DEFAULT_MODE, handle=None,
344344
use_errno=False,
345345
use_last_error=False,
346346
winmode=None):
347+
class _FuncPtr(_CFuncPtr):
348+
_flags_ = self._func_flags_
349+
_restype_ = self._func_restype_
350+
if use_errno:
351+
_flags_ |= _FUNCFLAG_USE_ERRNO
352+
if use_last_error:
353+
_flags_ |= _FUNCFLAG_USE_LASTERROR
354+
355+
self._FuncPtr = _FuncPtr
347356
if name:
348357
name = _os.fspath(name)
349358

359+
self._handle = self._load_library(name, mode, handle, winmode)
360+
361+
if _os.name == "nt":
362+
def _load_library(self, name, mode, handle, winmode):
363+
if winmode is None:
364+
import nt as _nt
365+
winmode = _nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
366+
# WINAPI LoadLibrary searches for a DLL if the given name
367+
# is not fully qualified with an explicit drive. For POSIX
368+
# compatibility, and because the DLL search path no longer
369+
# contains the working directory, begin by fully resolving
370+
# any name that contains a path separator.
371+
if name is not None and ('/' in name or '\\' in name):
372+
name = _nt._getfullpathname(name)
373+
winmode |= _nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
374+
self._name = name
375+
if handle is not None:
376+
return handle
377+
return _LoadLibrary(self._name, winmode)
378+
379+
else:
380+
def _load_library(self, name, mode, handle, winmode):
350381
# If the filename that has been provided is an iOS/tvOS/watchOS
351382
# .fwork file, dereference the location to the true origin of the
352383
# binary.
353-
if name.endswith(".fwork"):
384+
if name and name.endswith(".fwork"):
354385
with open(name) as f:
355386
name = _os.path.join(
356387
_os.path.dirname(_sys.executable),
357388
f.read().strip()
358389
)
359-
360-
self._name = name
361-
flags = self._func_flags_
362-
if use_errno:
363-
flags |= _FUNCFLAG_USE_ERRNO
364-
if use_last_error:
365-
flags |= _FUNCFLAG_USE_LASTERROR
366-
if _sys.platform.startswith("aix"):
367-
"""When the name contains ".a(" and ends with ")",
368-
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
369-
archive(member) syntax for dlopen(), and the mode is adjusted.
370-
Otherwise, name is presented to dlopen() as a file argument.
371-
"""
372-
if name and name.endswith(")") and ".a(" in name:
373-
mode |= ( _os.RTLD_MEMBER | _os.RTLD_NOW )
374-
if _os.name == "nt":
375-
if winmode is not None:
376-
mode = winmode
377-
else:
378-
import nt
379-
mode = nt._LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
380-
if '/' in name or '\\' in name:
381-
self._name = nt._getfullpathname(self._name)
382-
mode |= nt._LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR
383-
384-
class _FuncPtr(_CFuncPtr):
385-
_flags_ = flags
386-
_restype_ = self._func_restype_
387-
self._FuncPtr = _FuncPtr
388-
389-
if handle is None:
390-
self._handle = _dlopen(self._name, mode)
391-
else:
392-
self._handle = handle
390+
if _sys.platform.startswith("aix"):
391+
"""When the name contains ".a(" and ends with ")",
392+
e.g., "libFOO.a(libFOO.so)" - this is taken to be an
393+
archive(member) syntax for dlopen(), and the mode is adjusted.
394+
Otherwise, name is presented to dlopen() as a file argument.
395+
"""
396+
if name and name.endswith(")") and ".a(" in name:
397+
mode |= _os.RTLD_MEMBER | _os.RTLD_NOW
398+
self._name = name
399+
return _dlopen(name, mode)
393400

394401
def __repr__(self):
395402
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)