mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
gh-100926: Move ctype's pointers cache from _pointer_type_cache to StgInfo (GH-131282)
Deprecate _pointer_type_cache and calling POINTER on a string. Co-authored-by: neonene <53406459+neonene@users.noreply.github.com> Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com> Co-authored-by: Petr Viktorin <encukou@gmail.com>
This commit is contained in:
parent
7e7e49be78
commit
a0bc0c462f
17 changed files with 697 additions and 213 deletions
|
@ -2172,10 +2172,20 @@ Utility functions
|
|||
|
||||
.. function:: POINTER(type, /)
|
||||
|
||||
Create and return a new ctypes pointer type. Pointer types are cached and
|
||||
Create or return a ctypes pointer type. Pointer types are cached and
|
||||
reused internally, so calling this function repeatedly is cheap.
|
||||
*type* must be a ctypes type.
|
||||
|
||||
.. impl-detail::
|
||||
|
||||
The resulting pointer type is cached in the ``__pointer_type__``
|
||||
attribute of *type*.
|
||||
It is possible to set this attribute before the first call to
|
||||
``POINTER`` in order to set a custom pointer type.
|
||||
However, doing this is discouraged: manually creating a suitable
|
||||
pointer type is difficult without relying on implementation
|
||||
details that may change in future Python versions.
|
||||
|
||||
|
||||
.. function:: pointer(obj, /)
|
||||
|
||||
|
@ -2340,6 +2350,16 @@ Data types
|
|||
library. *name* is the name of the symbol that exports the data, *library*
|
||||
is the loaded shared library.
|
||||
|
||||
Common class variables of ctypes data types:
|
||||
|
||||
.. attribute:: __pointer_type__
|
||||
|
||||
The pointer type that was created by calling
|
||||
:func:`POINTER` for corresponding ctypes data type. If a pointer type
|
||||
was not yet created, the attribute is missing.
|
||||
|
||||
.. versionadded:: next
|
||||
|
||||
Common instance variables of ctypes data types:
|
||||
|
||||
.. attribute:: _b_base_
|
||||
|
|
|
@ -807,11 +807,16 @@ ctypes
|
|||
loaded by the current process.
|
||||
(Contributed by Brian Ward in :gh:`119349`.)
|
||||
|
||||
* Move :func:`ctypes.POINTER` types cache from a global internal cache
|
||||
(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
|
||||
attribute of the corresponding :mod:`ctypes` types.
|
||||
This will stop the cache from growing without limits in some situations.
|
||||
(Contributed by Sergey Miryanov in :gh:`100926`).
|
||||
|
||||
* The :class:`ctypes.py_object` type now supports subscription,
|
||||
making it a :term:`generic type`.
|
||||
(Contributed by Brian Schubert in :gh:`132168`.)
|
||||
|
||||
|
||||
datetime
|
||||
--------
|
||||
|
||||
|
@ -1679,6 +1684,13 @@ Deprecated
|
|||
:func:`codecs.open` is now deprecated. Use :func:`open` instead.
|
||||
(Contributed by Inada Naoki in :gh:`133036`.)
|
||||
|
||||
* :mod:`ctypes`:
|
||||
Calling :func:`ctypes.POINTER` on a string is deprecated.
|
||||
Use :ref:`ctypes-incomplete-types` for self-referential structures.
|
||||
Also, the internal ``ctypes._pointer_type_cache`` is deprecated.
|
||||
See :func:`ctypes.POINTER` for updated implementation details.
|
||||
(Contributed by Sergey Myrianov in :gh:`100926`.)
|
||||
|
||||
* :mod:`functools`:
|
||||
Calling the Python implementation of :func:`functools.reduce` with *function*
|
||||
or *sequence* as keyword arguments is now deprecated.
|
||||
|
|
|
@ -266,7 +266,72 @@ _check_size(c_void_p)
|
|||
class c_bool(_SimpleCData):
|
||||
_type_ = "?"
|
||||
|
||||
from _ctypes import POINTER, pointer, _pointer_type_cache
|
||||
def POINTER(cls):
|
||||
"""Create and return a new ctypes pointer type.
|
||||
|
||||
Pointer types are cached and reused internally,
|
||||
so calling this function repeatedly is cheap.
|
||||
"""
|
||||
if cls is None:
|
||||
return c_void_p
|
||||
try:
|
||||
return cls.__pointer_type__
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(cls, str):
|
||||
# handle old-style incomplete types (see test_ctypes.test_incomplete)
|
||||
import warnings
|
||||
warnings._deprecated("ctypes.POINTER with string", remove=(3, 19))
|
||||
try:
|
||||
return _pointer_type_cache_fallback[cls]
|
||||
except KeyError:
|
||||
result = type(f'LP_{cls}', (_Pointer,), {})
|
||||
_pointer_type_cache_fallback[cls] = result
|
||||
return result
|
||||
|
||||
# create pointer type and set __pointer_type__ for cls
|
||||
return type(f'LP_{cls.__name__}', (_Pointer,), {'_type_': cls})
|
||||
|
||||
def pointer(obj):
|
||||
"""Create a new pointer instance, pointing to 'obj'.
|
||||
|
||||
The returned object is of the type POINTER(type(obj)). Note that if you
|
||||
just want to pass a pointer to an object to a foreign function call, you
|
||||
should use byref(obj) which is much faster.
|
||||
"""
|
||||
typ = POINTER(type(obj))
|
||||
return typ(obj)
|
||||
|
||||
class _PointerTypeCache:
|
||||
def __setitem__(self, cls, pointer_type):
|
||||
import warnings
|
||||
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
|
||||
try:
|
||||
cls.__pointer_type__ = pointer_type
|
||||
except AttributeError:
|
||||
_pointer_type_cache_fallback[cls] = pointer_type
|
||||
|
||||
def __getitem__(self, cls):
|
||||
import warnings
|
||||
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
|
||||
try:
|
||||
return cls.__pointer_type__
|
||||
except AttributeError:
|
||||
return _pointer_type_cache_fallback[cls]
|
||||
|
||||
def get(self, cls, default=None):
|
||||
import warnings
|
||||
warnings._deprecated("ctypes._pointer_type_cache", remove=(3, 19))
|
||||
try:
|
||||
return cls.__pointer_type__
|
||||
except AttributeError:
|
||||
return _pointer_type_cache_fallback.get(cls, default)
|
||||
|
||||
def __contains__(self, cls):
|
||||
return hasattr(cls, '__pointer_type__')
|
||||
|
||||
_pointer_type_cache_fallback = {}
|
||||
_pointer_type_cache = _PointerTypeCache()
|
||||
|
||||
class c_wchar_p(_SimpleCData):
|
||||
_type_ = "Z"
|
||||
|
@ -277,7 +342,7 @@ class c_wchar(_SimpleCData):
|
|||
_type_ = "u"
|
||||
|
||||
def _reset_cache():
|
||||
_pointer_type_cache.clear()
|
||||
_pointer_type_cache_fallback.clear()
|
||||
_c_functype_cache.clear()
|
||||
if _os.name == "nt":
|
||||
_win_functype_cache.clear()
|
||||
|
@ -285,7 +350,6 @@ def _reset_cache():
|
|||
POINTER(c_wchar).from_param = c_wchar_p.from_param
|
||||
# _SimpleCData.c_char_p_from_param
|
||||
POINTER(c_char).from_param = c_char_p.from_param
|
||||
_pointer_type_cache[None] = c_void_p
|
||||
|
||||
def create_unicode_buffer(init, size=None):
|
||||
"""create_unicode_buffer(aString) -> character array
|
||||
|
@ -319,13 +383,7 @@ def create_unicode_buffer(init, size=None):
|
|||
def SetPointerType(pointer, cls):
|
||||
import warnings
|
||||
warnings._deprecated("ctypes.SetPointerType", remove=(3, 15))
|
||||
if _pointer_type_cache.get(cls, None) is not None:
|
||||
raise RuntimeError("This type already exists in the cache")
|
||||
if id(pointer) not in _pointer_type_cache:
|
||||
raise RuntimeError("What's this???")
|
||||
pointer.set_type(cls)
|
||||
_pointer_type_cache[cls] = pointer
|
||||
del _pointer_type_cache[id(pointer)]
|
||||
|
||||
def ARRAY(typ, len):
|
||||
return typ * len
|
||||
|
|
|
@ -232,7 +232,6 @@ class Test(unittest.TestCase, StructCheckMixin):
|
|||
self.assertEqual(len(data), sizeof(TestStructure))
|
||||
ptr = POINTER(TestStructure)
|
||||
s = cast(data, ptr)[0]
|
||||
del ctypes._pointer_type_cache[TestStructure]
|
||||
self.assertEqual(s.point.x, 1)
|
||||
self.assertEqual(s.point.y, 2)
|
||||
|
||||
|
@ -371,7 +370,6 @@ class Test(unittest.TestCase, StructCheckMixin):
|
|||
self.assertEqual(len(data), sizeof(TestUnion))
|
||||
ptr = POINTER(TestUnion)
|
||||
s = cast(data, ptr)[0]
|
||||
del ctypes._pointer_type_cache[TestUnion]
|
||||
self.assertEqual(s.point.x, 1)
|
||||
self.assertEqual(s.point.y, 2)
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import unittest
|
||||
from test.support import MS_WINDOWS
|
||||
import ctypes
|
||||
from ctypes import POINTER, c_void_p
|
||||
from ctypes import POINTER, Structure, c_void_p
|
||||
|
||||
from ._support import PyCSimpleType
|
||||
from ._support import PyCSimpleType, PyCPointerType, PyCStructType
|
||||
|
||||
|
||||
def set_non_ctypes_pointer_type(cls, pointer_type):
|
||||
cls.__pointer_type__ = pointer_type
|
||||
|
||||
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
# to not leak references, we must clean _pointer_type_cache
|
||||
ctypes._reset_cache()
|
||||
|
||||
def test_creating_pointer_in_dunder_new_1(self):
|
||||
# Test metaclass whose instances are C types; when the type is
|
||||
# created it automatically creates a pointer type for itself.
|
||||
|
@ -36,7 +35,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
else:
|
||||
ptr_bases = (self, POINTER(bases[0]))
|
||||
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
|
||||
ctypes._pointer_type_cache[self] = p
|
||||
set_non_ctypes_pointer_type(self, p)
|
||||
return self
|
||||
|
||||
class p_meta(PyCSimpleType, ct_meta):
|
||||
|
@ -45,20 +44,36 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
class PtrBase(c_void_p, metaclass=p_meta):
|
||||
pass
|
||||
|
||||
ptr_base_pointer = POINTER(PtrBase)
|
||||
|
||||
class CtBase(object, metaclass=ct_meta):
|
||||
pass
|
||||
|
||||
ct_base_pointer = POINTER(CtBase)
|
||||
|
||||
class Sub(CtBase):
|
||||
pass
|
||||
|
||||
sub_pointer = POINTER(Sub)
|
||||
|
||||
class Sub2(Sub):
|
||||
pass
|
||||
|
||||
sub2_pointer = POINTER(Sub2)
|
||||
|
||||
self.assertIsNot(ptr_base_pointer, ct_base_pointer)
|
||||
self.assertIsNot(ct_base_pointer, sub_pointer)
|
||||
self.assertIsNot(sub_pointer, sub2_pointer)
|
||||
|
||||
self.assertIsInstance(POINTER(Sub2), p_meta)
|
||||
self.assertIsSubclass(POINTER(Sub2), Sub2)
|
||||
self.assertIsSubclass(POINTER(Sub2), POINTER(Sub))
|
||||
self.assertIsSubclass(POINTER(Sub), POINTER(CtBase))
|
||||
|
||||
self.assertIs(POINTER(Sub2), sub2_pointer)
|
||||
self.assertIs(POINTER(Sub), sub_pointer)
|
||||
self.assertIs(POINTER(CtBase), ct_base_pointer)
|
||||
|
||||
def test_creating_pointer_in_dunder_new_2(self):
|
||||
# A simpler variant of the above, used in `CoClass` of the `comtypes`
|
||||
# project.
|
||||
|
@ -69,7 +84,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
if isinstance(self, p_meta):
|
||||
return self
|
||||
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
|
||||
ctypes._pointer_type_cache[self] = p
|
||||
set_non_ctypes_pointer_type(self, p)
|
||||
return self
|
||||
|
||||
class p_meta(PyCSimpleType, ct_meta):
|
||||
|
@ -78,15 +93,27 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
class Core(object):
|
||||
pass
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "must have storage info"):
|
||||
POINTER(Core)
|
||||
|
||||
class CtBase(Core, metaclass=ct_meta):
|
||||
pass
|
||||
|
||||
ct_base_pointer = POINTER(CtBase)
|
||||
|
||||
class Sub(CtBase):
|
||||
pass
|
||||
|
||||
sub_pointer = POINTER(Sub)
|
||||
|
||||
self.assertIsNot(ct_base_pointer, sub_pointer)
|
||||
|
||||
self.assertIsInstance(POINTER(Sub), p_meta)
|
||||
self.assertIsSubclass(POINTER(Sub), Sub)
|
||||
|
||||
self.assertIs(POINTER(Sub), sub_pointer)
|
||||
self.assertIs(POINTER(CtBase), ct_base_pointer)
|
||||
|
||||
def test_creating_pointer_in_dunder_init_1(self):
|
||||
class ct_meta(type):
|
||||
def __init__(self, name, bases, namespace):
|
||||
|
@ -103,7 +130,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
else:
|
||||
ptr_bases = (self, POINTER(bases[0]))
|
||||
p = p_meta(f"POINTER({self.__name__})", ptr_bases, {})
|
||||
ctypes._pointer_type_cache[self] = p
|
||||
set_non_ctypes_pointer_type(self, p)
|
||||
|
||||
class p_meta(PyCSimpleType, ct_meta):
|
||||
pass
|
||||
|
@ -111,20 +138,37 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
class PtrBase(c_void_p, metaclass=p_meta):
|
||||
pass
|
||||
|
||||
ptr_base_pointer = POINTER(PtrBase)
|
||||
|
||||
class CtBase(object, metaclass=ct_meta):
|
||||
pass
|
||||
|
||||
ct_base_pointer = POINTER(CtBase)
|
||||
|
||||
class Sub(CtBase):
|
||||
pass
|
||||
|
||||
sub_pointer = POINTER(Sub)
|
||||
|
||||
class Sub2(Sub):
|
||||
pass
|
||||
|
||||
sub2_pointer = POINTER(Sub2)
|
||||
|
||||
self.assertIsNot(ptr_base_pointer, ct_base_pointer)
|
||||
self.assertIsNot(ct_base_pointer, sub_pointer)
|
||||
self.assertIsNot(sub_pointer, sub2_pointer)
|
||||
|
||||
self.assertIsInstance(POINTER(Sub2), p_meta)
|
||||
self.assertIsSubclass(POINTER(Sub2), Sub2)
|
||||
self.assertIsSubclass(POINTER(Sub2), POINTER(Sub))
|
||||
self.assertIsSubclass(POINTER(Sub), POINTER(CtBase))
|
||||
|
||||
self.assertIs(POINTER(PtrBase), ptr_base_pointer)
|
||||
self.assertIs(POINTER(CtBase), ct_base_pointer)
|
||||
self.assertIs(POINTER(Sub), sub_pointer)
|
||||
self.assertIs(POINTER(Sub2), sub2_pointer)
|
||||
|
||||
def test_creating_pointer_in_dunder_init_2(self):
|
||||
class ct_meta(type):
|
||||
def __init__(self, name, bases, namespace):
|
||||
|
@ -135,7 +179,7 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
if isinstance(self, p_meta):
|
||||
return
|
||||
p = p_meta(f"POINTER({self.__name__})", (self, c_void_p), {})
|
||||
ctypes._pointer_type_cache[self] = p
|
||||
set_non_ctypes_pointer_type(self, p)
|
||||
|
||||
class p_meta(PyCSimpleType, ct_meta):
|
||||
pass
|
||||
|
@ -146,12 +190,21 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
class CtBase(Core, metaclass=ct_meta):
|
||||
pass
|
||||
|
||||
ct_base_pointer = POINTER(CtBase)
|
||||
|
||||
class Sub(CtBase):
|
||||
pass
|
||||
|
||||
sub_pointer = POINTER(Sub)
|
||||
|
||||
self.assertIsNot(ct_base_pointer, sub_pointer)
|
||||
|
||||
self.assertIsInstance(POINTER(Sub), p_meta)
|
||||
self.assertIsSubclass(POINTER(Sub), Sub)
|
||||
|
||||
self.assertIs(POINTER(CtBase), ct_base_pointer)
|
||||
self.assertIs(POINTER(Sub), sub_pointer)
|
||||
|
||||
def test_bad_type_message(self):
|
||||
"""Verify the error message that lists all available type codes"""
|
||||
# (The string is generated at runtime, so this checks the underlying
|
||||
|
@ -168,3 +221,164 @@ class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
|
|||
if not MS_WINDOWS:
|
||||
expected_type_chars.remove('X')
|
||||
self.assertIn("'" + ''.join(expected_type_chars) + "'", message)
|
||||
|
||||
def test_creating_pointer_in_dunder_init_3(self):
|
||||
"""Check if interfcase subclasses properly creates according internal
|
||||
pointer types. But not the same as external pointer types.
|
||||
"""
|
||||
|
||||
class StructureMeta(PyCStructType):
|
||||
def __new__(cls, name, bases, dct, /, create_pointer_type=True):
|
||||
assert len(bases) == 1, bases
|
||||
return super().__new__(cls, name, bases, dct)
|
||||
|
||||
def __init__(self, name, bases, dct, /, create_pointer_type=True):
|
||||
|
||||
super().__init__(name, bases, dct)
|
||||
if create_pointer_type:
|
||||
p_bases = (POINTER(bases[0]),)
|
||||
ns = {'_type_': self}
|
||||
internal_pointer_type = PointerMeta(f"p{name}", p_bases, ns)
|
||||
assert isinstance(internal_pointer_type, PyCPointerType)
|
||||
assert self.__pointer_type__ is internal_pointer_type
|
||||
|
||||
class PointerMeta(PyCPointerType):
|
||||
def __new__(cls, name, bases, dct):
|
||||
target = dct.get('_type_', None)
|
||||
if target is None:
|
||||
|
||||
# Create corresponding interface type and then set it as target
|
||||
target = StructureMeta(
|
||||
f"_{name}_",
|
||||
(bases[0]._type_,),
|
||||
{},
|
||||
create_pointer_type=False
|
||||
)
|
||||
dct['_type_'] = target
|
||||
|
||||
pointer_type = super().__new__(cls, name, bases, dct)
|
||||
assert not hasattr(target, '__pointer_type__')
|
||||
|
||||
return pointer_type
|
||||
|
||||
def __init__(self, name, bases, dct, /, create_pointer_type=True):
|
||||
target = dct.get('_type_', None)
|
||||
assert not hasattr(target, '__pointer_type__')
|
||||
super().__init__(name, bases, dct)
|
||||
assert target.__pointer_type__ is self
|
||||
|
||||
|
||||
class Interface(Structure, metaclass=StructureMeta, create_pointer_type=False):
|
||||
pass
|
||||
|
||||
class pInterface(POINTER(c_void_p), metaclass=PointerMeta):
|
||||
_type_ = Interface
|
||||
|
||||
class IUnknown(Interface):
|
||||
pass
|
||||
|
||||
class pIUnknown(pInterface):
|
||||
pass
|
||||
|
||||
self.assertTrue(issubclass(POINTER(IUnknown), pInterface))
|
||||
|
||||
self.assertIs(POINTER(Interface), pInterface)
|
||||
self.assertIsNot(POINTER(IUnknown), pIUnknown)
|
||||
|
||||
def test_creating_pointer_in_dunder_init_4(self):
|
||||
"""Check if interfcase subclasses properly creates according internal
|
||||
pointer types, the same as external pointer types.
|
||||
"""
|
||||
class StructureMeta(PyCStructType):
|
||||
def __new__(cls, name, bases, dct, /, create_pointer_type=True):
|
||||
assert len(bases) == 1, bases
|
||||
|
||||
return super().__new__(cls, name, bases, dct)
|
||||
|
||||
def __init__(self, name, bases, dct, /, create_pointer_type=True):
|
||||
|
||||
super().__init__(name, bases, dct)
|
||||
if create_pointer_type:
|
||||
p_bases = (POINTER(bases[0]),)
|
||||
ns = {'_type_': self}
|
||||
internal_pointer_type = PointerMeta(f"p{name}", p_bases, ns)
|
||||
assert isinstance(internal_pointer_type, PyCPointerType)
|
||||
assert self.__pointer_type__ is internal_pointer_type
|
||||
|
||||
class PointerMeta(PyCPointerType):
|
||||
def __new__(cls, name, bases, dct):
|
||||
target = dct.get('_type_', None)
|
||||
assert target is not None
|
||||
pointer_type = getattr(target, '__pointer_type__', None)
|
||||
|
||||
if pointer_type is None:
|
||||
pointer_type = super().__new__(cls, name, bases, dct)
|
||||
|
||||
return pointer_type
|
||||
|
||||
def __init__(self, name, bases, dct, /, create_pointer_type=True):
|
||||
target = dct.get('_type_', None)
|
||||
if not hasattr(target, '__pointer_type__'):
|
||||
# target.__pointer_type__ was created by super().__new__
|
||||
super().__init__(name, bases, dct)
|
||||
|
||||
assert target.__pointer_type__ is self
|
||||
|
||||
|
||||
class Interface(Structure, metaclass=StructureMeta, create_pointer_type=False):
|
||||
pass
|
||||
|
||||
class pInterface(POINTER(c_void_p), metaclass=PointerMeta):
|
||||
_type_ = Interface
|
||||
|
||||
class IUnknown(Interface):
|
||||
pass
|
||||
|
||||
class pIUnknown(pInterface):
|
||||
_type_ = IUnknown
|
||||
|
||||
self.assertTrue(issubclass(POINTER(IUnknown), pInterface))
|
||||
|
||||
self.assertIs(POINTER(Interface), pInterface)
|
||||
self.assertIs(POINTER(IUnknown), pIUnknown)
|
||||
|
||||
def test_custom_pointer_cache_for_ctypes_type1(self):
|
||||
# Check if PyCPointerType.__init__() caches a pointer type
|
||||
# customized in the metatype's __new__().
|
||||
class PointerMeta(PyCPointerType):
|
||||
def __new__(cls, name, bases, namespace):
|
||||
namespace["_type_"] = C
|
||||
return super().__new__(cls, name, bases, namespace)
|
||||
|
||||
def __init__(self, name, bases, namespace):
|
||||
assert not hasattr(C, '__pointer_type__')
|
||||
super().__init__(name, bases, namespace)
|
||||
assert C.__pointer_type__ is self
|
||||
|
||||
class C(c_void_p): # ctypes type
|
||||
pass
|
||||
|
||||
class P(ctypes._Pointer, metaclass=PointerMeta):
|
||||
pass
|
||||
|
||||
self.assertIs(P._type_, C)
|
||||
self.assertIs(P, POINTER(C))
|
||||
|
||||
def test_custom_pointer_cache_for_ctypes_type2(self):
|
||||
# Check if PyCPointerType.__init__() caches a pointer type
|
||||
# customized in the metatype's __init__().
|
||||
class PointerMeta(PyCPointerType):
|
||||
def __init__(self, name, bases, namespace):
|
||||
self._type_ = namespace["_type_"] = C
|
||||
assert not hasattr(C, '__pointer_type__')
|
||||
super().__init__(name, bases, namespace)
|
||||
assert C.__pointer_type__ is self
|
||||
|
||||
class C(c_void_p): # ctypes type
|
||||
pass
|
||||
|
||||
class P(ctypes._Pointer, metaclass=PointerMeta):
|
||||
pass
|
||||
|
||||
self.assertIs(P._type_, C)
|
||||
self.assertIs(P, POINTER(C))
|
||||
|
|
|
@ -3,15 +3,20 @@ import unittest
|
|||
import warnings
|
||||
from ctypes import Structure, POINTER, pointer, c_char_p
|
||||
|
||||
# String-based "incomplete pointers" were implemented in ctypes 0.6.3 (2003, when
|
||||
# ctypes was an external project). They made obsolete by the current
|
||||
# incomplete *types* (setting `_fields_` late) in 0.9.5 (2005).
|
||||
# ctypes was added to Python 2.5 (2006), without any mention in docs.
|
||||
|
||||
# The incomplete pointer example from the tutorial
|
||||
# This tests incomplete pointer example from the old tutorial
|
||||
# (https://svn.python.org/projects/ctypes/tags/release_0_6_3/ctypes/docs/tutorial.stx)
|
||||
class TestSetPointerType(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
# to not leak references, we must clean _pointer_type_cache
|
||||
ctypes._reset_cache()
|
||||
ctypes._pointer_type_cache_fallback.clear()
|
||||
|
||||
def test_incomplete_example(self):
|
||||
lpcell = POINTER("cell")
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
lpcell = POINTER("cell")
|
||||
class cell(Structure):
|
||||
_fields_ = [("name", c_char_p),
|
||||
("next", lpcell)]
|
||||
|
@ -20,6 +25,8 @@ class TestSetPointerType(unittest.TestCase):
|
|||
warnings.simplefilter('ignore', DeprecationWarning)
|
||||
ctypes.SetPointerType(lpcell, cell)
|
||||
|
||||
self.assertIs(POINTER(cell), lpcell)
|
||||
|
||||
c1 = cell()
|
||||
c1.name = b"foo"
|
||||
c2 = cell()
|
||||
|
@ -37,7 +44,8 @@ class TestSetPointerType(unittest.TestCase):
|
|||
self.assertEqual(result, [b"foo", b"bar"] * 4)
|
||||
|
||||
def test_deprecation(self):
|
||||
lpcell = POINTER("cell")
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
lpcell = POINTER("cell")
|
||||
class cell(Structure):
|
||||
_fields_ = [("name", c_char_p),
|
||||
("next", lpcell)]
|
||||
|
@ -45,6 +53,7 @@ class TestSetPointerType(unittest.TestCase):
|
|||
with self.assertWarns(DeprecationWarning):
|
||||
ctypes.SetPointerType(lpcell, cell)
|
||||
|
||||
self.assertIs(POINTER(cell), lpcell)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import unittest
|
||||
from ctypes import (Structure, POINTER, pointer, _pointer_type_cache,
|
||||
c_char_p, c_int)
|
||||
from ctypes import (Structure, POINTER, pointer, c_char_p, c_int)
|
||||
|
||||
|
||||
class SimpleTestCase(unittest.TestCase):
|
||||
|
@ -115,10 +114,6 @@ class PointerToStructure(unittest.TestCase):
|
|||
r.a[0].x = 42
|
||||
r.a[0].y = 99
|
||||
|
||||
# to avoid leaking when tests are run several times
|
||||
# clean up the types left in the cache.
|
||||
del _pointer_type_cache[POINT]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import array
|
||||
import ctypes
|
||||
import gc
|
||||
import sys
|
||||
import unittest
|
||||
from ctypes import (CDLL, CFUNCTYPE, Structure,
|
||||
POINTER, pointer, _Pointer, _pointer_type_cache,
|
||||
POINTER, pointer, _Pointer,
|
||||
byref, sizeof,
|
||||
c_void_p, c_char_p,
|
||||
c_byte, c_ubyte, c_short, c_ushort, c_int, c_uint,
|
||||
c_long, c_ulong, c_longlong, c_ulonglong,
|
||||
c_float, c_double)
|
||||
from ctypes import _pointer_type_cache, _pointer_type_cache_fallback
|
||||
from test.support import import_helper
|
||||
from weakref import WeakSet
|
||||
_ctypes_test = import_helper.import_module("_ctypes_test")
|
||||
from ._support import (_CData, PyCPointerType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
|
||||
Py_TPFLAGS_IMMUTABLETYPE)
|
||||
|
@ -22,6 +25,9 @@ python_types = [int, int, int, int, int, int,
|
|||
|
||||
|
||||
class PointersTestCase(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
_pointer_type_cache_fallback.clear()
|
||||
|
||||
def test_inheritance_hierarchy(self):
|
||||
self.assertEqual(_Pointer.mro(), [_Pointer, _CData, object])
|
||||
|
||||
|
@ -127,6 +133,14 @@ class PointersTestCase(unittest.TestCase):
|
|||
addr = a.buffer_info()[0]
|
||||
p = POINTER(POINTER(c_int))
|
||||
|
||||
def test_pointer_from_pointer(self):
|
||||
p1 = POINTER(c_int)
|
||||
p2 = POINTER(p1)
|
||||
|
||||
self.assertIsNot(p1, p2)
|
||||
self.assertIs(p1.__pointer_type__, p2)
|
||||
self.assertIs(p2._type_, p1)
|
||||
|
||||
def test_other(self):
|
||||
class Table(Structure):
|
||||
_fields_ = [("a", c_int),
|
||||
|
@ -141,8 +155,6 @@ class PointersTestCase(unittest.TestCase):
|
|||
|
||||
pt.contents.c = 33
|
||||
|
||||
del _pointer_type_cache[Table]
|
||||
|
||||
def test_basic(self):
|
||||
p = pointer(c_int(42))
|
||||
# Although a pointer can be indexed, it has no length
|
||||
|
@ -175,6 +187,7 @@ class PointersTestCase(unittest.TestCase):
|
|||
q = pointer(y)
|
||||
pp[0] = q # <==
|
||||
self.assertEqual(p[0], 6)
|
||||
|
||||
def test_c_void_p(self):
|
||||
# http://sourceforge.net/tracker/?func=detail&aid=1518190&group_id=5470&atid=105470
|
||||
if sizeof(c_void_p) == 4:
|
||||
|
@ -193,6 +206,30 @@ class PointersTestCase(unittest.TestCase):
|
|||
self.assertRaises(TypeError, c_void_p, 3.14) # make sure floats are NOT accepted
|
||||
self.assertRaises(TypeError, c_void_p, object()) # nor other objects
|
||||
|
||||
def test_read_null_pointer(self):
|
||||
null_ptr = POINTER(c_int)()
|
||||
with self.assertRaisesRegex(ValueError, "NULL pointer access"):
|
||||
null_ptr[0]
|
||||
|
||||
def test_write_null_pointer(self):
|
||||
null_ptr = POINTER(c_int)()
|
||||
with self.assertRaisesRegex(ValueError, "NULL pointer access"):
|
||||
null_ptr[0] = 1
|
||||
|
||||
def test_set_pointer_to_null_and_read(self):
|
||||
class Bar(Structure):
|
||||
_fields_ = [("values", POINTER(c_int))]
|
||||
|
||||
bar = Bar()
|
||||
bar.values = (c_int * 3)(1, 2, 3)
|
||||
|
||||
values = [bar.values[0], bar.values[1], bar.values[2]]
|
||||
self.assertEqual(values, [1, 2, 3])
|
||||
|
||||
bar.values = None
|
||||
with self.assertRaisesRegex(ValueError, "NULL pointer access"):
|
||||
bar.values[0]
|
||||
|
||||
def test_pointers_bool(self):
|
||||
# NULL pointers have a boolean False value, non-NULL pointers True.
|
||||
self.assertEqual(bool(POINTER(c_int)()), False)
|
||||
|
@ -210,20 +247,220 @@ class PointersTestCase(unittest.TestCase):
|
|||
LargeNamedType = type('T' * 2 ** 25, (Structure,), {})
|
||||
self.assertTrue(POINTER(LargeNamedType))
|
||||
|
||||
# to not leak references, we must clean _pointer_type_cache
|
||||
del _pointer_type_cache[LargeNamedType]
|
||||
|
||||
def test_pointer_type_str_name(self):
|
||||
large_string = 'T' * 2 ** 25
|
||||
P = POINTER(large_string)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
P = POINTER(large_string)
|
||||
self.assertTrue(P)
|
||||
|
||||
# to not leak references, we must clean _pointer_type_cache
|
||||
del _pointer_type_cache[id(P)]
|
||||
|
||||
def test_abstract(self):
|
||||
self.assertRaises(TypeError, _Pointer.set_type, 42)
|
||||
|
||||
def test_pointer_types_equal(self):
|
||||
t1 = POINTER(c_int)
|
||||
t2 = POINTER(c_int)
|
||||
|
||||
self.assertIs(t1, t2)
|
||||
|
||||
p1 = t1(c_int(1))
|
||||
p2 = pointer(c_int(1))
|
||||
|
||||
self.assertIsInstance(p1, t1)
|
||||
self.assertIsInstance(p2, t1)
|
||||
|
||||
self.assertIs(type(p1), t1)
|
||||
self.assertIs(type(p2), t1)
|
||||
|
||||
def test_incomplete_pointer_types_still_equal(self):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
t1 = POINTER("LP_C")
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
t2 = POINTER("LP_C")
|
||||
|
||||
self.assertIs(t1, t2)
|
||||
|
||||
def test_incomplete_pointer_types_cannot_instantiate(self):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
t1 = POINTER("LP_C")
|
||||
with self.assertRaisesRegex(TypeError, "has no _type_"):
|
||||
t1()
|
||||
|
||||
def test_pointer_set_type_twice(self):
|
||||
t1 = POINTER(c_int)
|
||||
self.assertIs(c_int.__pointer_type__, t1)
|
||||
self.assertIs(t1._type_, c_int)
|
||||
|
||||
t1.set_type(c_int)
|
||||
self.assertIs(c_int.__pointer_type__, t1)
|
||||
self.assertIs(t1._type_, c_int)
|
||||
|
||||
def test_pointer_set_wrong_type(self):
|
||||
int_ptr = POINTER(c_int)
|
||||
float_ptr = POINTER(c_float)
|
||||
try:
|
||||
class C(c_int):
|
||||
pass
|
||||
|
||||
t1 = POINTER(c_int)
|
||||
t2 = POINTER(c_float)
|
||||
t1.set_type(c_float)
|
||||
self.assertEqual(t1(c_float(1.5))[0], 1.5)
|
||||
self.assertIs(t1._type_, c_float)
|
||||
self.assertIs(c_int.__pointer_type__, t1)
|
||||
self.assertIs(c_float.__pointer_type__, float_ptr)
|
||||
|
||||
t1.set_type(C)
|
||||
self.assertEqual(t1(C(123))[0].value, 123)
|
||||
self.assertIs(c_int.__pointer_type__, t1)
|
||||
self.assertIs(c_float.__pointer_type__, float_ptr)
|
||||
finally:
|
||||
POINTER(c_int).set_type(c_int)
|
||||
self.assertIs(POINTER(c_int), int_ptr)
|
||||
self.assertIs(POINTER(c_int)._type_, c_int)
|
||||
self.assertIs(c_int.__pointer_type__, int_ptr)
|
||||
|
||||
def test_pointer_not_ctypes_type(self):
|
||||
with self.assertRaisesRegex(TypeError, "must have storage info"):
|
||||
POINTER(int)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "must have storage info"):
|
||||
pointer(int)
|
||||
|
||||
with self.assertRaisesRegex(TypeError, "must have storage info"):
|
||||
pointer(int(1))
|
||||
|
||||
def test_pointer_set_python_type(self):
|
||||
p1 = POINTER(c_int)
|
||||
with self.assertRaisesRegex(TypeError, "must have storage info"):
|
||||
p1.set_type(int)
|
||||
|
||||
def test_pointer_type_attribute_is_none(self):
|
||||
class Cls(Structure):
|
||||
_fields_ = (
|
||||
('a', c_int),
|
||||
('b', c_float),
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(AttributeError, ".Cls'> has no attribute '__pointer_type__'"):
|
||||
Cls.__pointer_type__
|
||||
|
||||
p = POINTER(Cls)
|
||||
self.assertIs(Cls.__pointer_type__, p)
|
||||
|
||||
def test_arbitrary_pointer_type_attribute(self):
|
||||
class Cls(Structure):
|
||||
_fields_ = (
|
||||
('a', c_int),
|
||||
('b', c_float),
|
||||
)
|
||||
|
||||
garbage = 'garbage'
|
||||
|
||||
P = POINTER(Cls)
|
||||
self.assertIs(Cls.__pointer_type__, P)
|
||||
Cls.__pointer_type__ = garbage
|
||||
self.assertIs(Cls.__pointer_type__, garbage)
|
||||
self.assertIs(POINTER(Cls), garbage)
|
||||
self.assertIs(P._type_, Cls)
|
||||
|
||||
instance = Cls(1, 2.0)
|
||||
pointer = P(instance)
|
||||
self.assertEqual(pointer[0].a, 1)
|
||||
self.assertEqual(pointer[0].b, 2)
|
||||
|
||||
del Cls.__pointer_type__
|
||||
|
||||
NewP = POINTER(Cls)
|
||||
self.assertIsNot(NewP, P)
|
||||
self.assertIs(Cls.__pointer_type__, NewP)
|
||||
self.assertIs(P._type_, Cls)
|
||||
|
||||
def test_pointer_types_factory(self):
|
||||
"""Shouldn't leak"""
|
||||
def factory():
|
||||
class Cls(Structure):
|
||||
_fields_ = (
|
||||
('a', c_int),
|
||||
('b', c_float),
|
||||
)
|
||||
|
||||
return Cls
|
||||
|
||||
ws_typ = WeakSet()
|
||||
ws_ptr = WeakSet()
|
||||
for _ in range(10):
|
||||
typ = factory()
|
||||
ptr = POINTER(typ)
|
||||
|
||||
ws_typ.add(typ)
|
||||
ws_ptr.add(ptr)
|
||||
|
||||
typ = None
|
||||
ptr = None
|
||||
|
||||
gc.collect()
|
||||
|
||||
self.assertEqual(len(ws_typ), 0, ws_typ)
|
||||
self.assertEqual(len(ws_ptr), 0, ws_ptr)
|
||||
|
||||
|
||||
class PointerTypeCacheTestCase(unittest.TestCase):
|
||||
# dummy tests to check warnings and base behavior
|
||||
def tearDown(self):
|
||||
_pointer_type_cache_fallback.clear()
|
||||
|
||||
def test_deprecated_cache_with_not_ctypes_type(self):
|
||||
class C:
|
||||
pass
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
P = POINTER("C")
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIs(_pointer_type_cache["C"], P)
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
_pointer_type_cache[C] = P
|
||||
self.assertIs(C.__pointer_type__, P)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIs(_pointer_type_cache[C], P)
|
||||
|
||||
def test_deprecated_cache_with_ints(self):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
_pointer_type_cache[123] = 456
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertEqual(_pointer_type_cache[123], 456)
|
||||
|
||||
def test_deprecated_cache_with_ctypes_type(self):
|
||||
class C(Structure):
|
||||
_fields_ = [("a", c_int),
|
||||
("b", c_int),
|
||||
("c", c_int)]
|
||||
|
||||
P1 = POINTER(C)
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
P2 = POINTER("C")
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
_pointer_type_cache[C] = P2
|
||||
|
||||
self.assertIs(C.__pointer_type__, P2)
|
||||
self.assertIsNot(C.__pointer_type__, P1)
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIs(_pointer_type_cache[C], P2)
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIs(_pointer_type_cache.get(C), P2)
|
||||
|
||||
def test_get_not_registered(self):
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIsNone(_pointer_type_cache.get(str))
|
||||
|
||||
with self.assertWarns(DeprecationWarning):
|
||||
self.assertIsNone(_pointer_type_cache.get(str, None))
|
||||
|
||||
def test_repeated_set_type(self):
|
||||
# Regression test for gh-133290
|
||||
class C(Structure):
|
||||
|
|
|
@ -685,6 +685,30 @@ class StructureTestCase(unittest.TestCase, StructCheckMixin):
|
|||
self.assertEqual(ctx.exception.args[0], 'item 1 in _argtypes_ passes '
|
||||
'a union by value, which is unsupported.')
|
||||
|
||||
def test_do_not_share_pointer_type_cache_via_stginfo_clone(self):
|
||||
# This test case calls PyCStgInfo_clone()
|
||||
# for the Mid and Vector class definitions
|
||||
# and checks that pointer_type cache not shared
|
||||
# between subclasses.
|
||||
class Base(Structure):
|
||||
_fields_ = [('y', c_double),
|
||||
('x', c_double)]
|
||||
base_ptr = POINTER(Base)
|
||||
|
||||
class Mid(Base):
|
||||
pass
|
||||
Mid._fields_ = []
|
||||
mid_ptr = POINTER(Mid)
|
||||
|
||||
class Vector(Mid):
|
||||
pass
|
||||
|
||||
vector_ptr = POINTER(Vector)
|
||||
|
||||
self.assertIsNot(base_ptr, mid_ptr)
|
||||
self.assertIsNot(base_ptr, vector_ptr)
|
||||
self.assertIsNot(mid_ptr, vector_ptr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -7,7 +7,6 @@ import importlib.util
|
|||
import sys
|
||||
import unittest
|
||||
from ctypes import (Structure, CDLL, POINTER, pythonapi,
|
||||
_pointer_type_cache,
|
||||
c_ubyte, c_char_p, c_int)
|
||||
from test.support import import_helper, thread_unsafe
|
||||
|
||||
|
@ -98,8 +97,6 @@ class PythonValuesTestCase(unittest.TestCase):
|
|||
"_PyImport_FrozenBootstrap example "
|
||||
"in Doc/library/ctypes.rst may be out of date")
|
||||
|
||||
del _pointer_type_cache[struct_frozen]
|
||||
|
||||
def test_undefined(self):
|
||||
self.assertRaises(ValueError, c_int.in_dll, pythonapi,
|
||||
"Undefined_Symbol")
|
||||
|
|
|
@ -5,7 +5,6 @@ import errno
|
|||
import sys
|
||||
import unittest
|
||||
from ctypes import (CDLL, Structure, POINTER, pointer, sizeof, byref,
|
||||
_pointer_type_cache,
|
||||
c_void_p, c_char, c_int, c_long)
|
||||
from test import support
|
||||
from test.support import import_helper
|
||||
|
@ -145,8 +144,8 @@ class Structures(unittest.TestCase):
|
|||
self.assertEqual(ret.top, top.value)
|
||||
self.assertEqual(ret.bottom, bottom.value)
|
||||
|
||||
# to not leak references, we must clean _pointer_type_cache
|
||||
del _pointer_type_cache[RECT]
|
||||
self.assertIs(PointInRect.argtypes[0], ReturnRect.argtypes[2])
|
||||
self.assertIs(PointInRect.argtypes[0], ReturnRect.argtypes[5])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Move :func:`ctypes.POINTER` types cache from a global internal cache
|
||||
(``_pointer_type_cache``) to the :attr:`ctypes._CData.__pointer_type__`
|
||||
attribute of the corresponding :mod:`ctypes` types.
|
||||
This will stop the cache from growing without limits in some situations.
|
|
@ -478,6 +478,7 @@ CType_Type_traverse(PyObject *self, visitproc visit, void *arg)
|
|||
Py_VISIT(info->converters);
|
||||
Py_VISIT(info->restype);
|
||||
Py_VISIT(info->checker);
|
||||
Py_VISIT(info->pointer_type);
|
||||
Py_VISIT(info->module);
|
||||
}
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
|
@ -493,7 +494,22 @@ ctype_clear_stginfo(StgInfo *info)
|
|||
Py_CLEAR(info->converters);
|
||||
Py_CLEAR(info->restype);
|
||||
Py_CLEAR(info->checker);
|
||||
Py_CLEAR(info->module);
|
||||
Py_CLEAR(info->pointer_type);
|
||||
Py_CLEAR(info->module); // decref the module last
|
||||
}
|
||||
|
||||
void
|
||||
ctype_free_stginfo_members(StgInfo *info)
|
||||
{
|
||||
assert(info);
|
||||
|
||||
PyMem_Free(info->ffi_type_pointer.elements);
|
||||
info->ffi_type_pointer.elements = NULL;
|
||||
PyMem_Free(info->format);
|
||||
info->format = NULL;
|
||||
PyMem_Free(info->shape);
|
||||
info->shape = NULL;
|
||||
ctype_clear_stginfo(info);
|
||||
}
|
||||
|
||||
static int
|
||||
|
@ -519,13 +535,7 @@ CType_Type_dealloc(PyObject *self)
|
|||
"deallocating ctypes %R", self);
|
||||
}
|
||||
if (info) {
|
||||
PyMem_Free(info->ffi_type_pointer.elements);
|
||||
info->ffi_type_pointer.elements = NULL;
|
||||
PyMem_Free(info->format);
|
||||
info->format = NULL;
|
||||
PyMem_Free(info->shape);
|
||||
info->shape = NULL;
|
||||
ctype_clear_stginfo(info);
|
||||
ctype_free_stginfo_members(info);
|
||||
}
|
||||
|
||||
PyTypeObject *tp = Py_TYPE(self);
|
||||
|
@ -566,6 +576,46 @@ _ctypes_CType_Type___sizeof___impl(PyObject *self, PyTypeObject *cls)
|
|||
return PyLong_FromSsize_t(size);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
ctype_get_pointer_type(PyObject *self, void *Py_UNUSED(ignored))
|
||||
{
|
||||
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
|
||||
StgInfo *info;
|
||||
if (PyStgInfo_FromType(st, self, &info) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (!info) {
|
||||
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (info->pointer_type) {
|
||||
return Py_NewRef(info->pointer_type);
|
||||
}
|
||||
|
||||
PyErr_Format(PyExc_AttributeError,
|
||||
"%R has no attribute '__pointer_type__'",
|
||||
self);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
ctype_set_pointer_type(PyObject *self, PyObject *tp, void *Py_UNUSED(ignored))
|
||||
{
|
||||
ctypes_state *st = get_module_state_by_def(Py_TYPE(self));
|
||||
StgInfo *info;
|
||||
if (PyStgInfo_FromType(st, self, &info) < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (!info) {
|
||||
PyErr_Format(PyExc_TypeError, "%R must have storage info", self);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Py_XSETREF(info->pointer_type, Py_XNewRef(tp));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
CType_Type_repeat(PyObject *self, Py_ssize_t length);
|
||||
|
||||
|
@ -575,12 +625,19 @@ static PyMethodDef ctype_methods[] = {
|
|||
{0},
|
||||
};
|
||||
|
||||
static PyGetSetDef ctype_getsets[] = {
|
||||
{ "__pointer_type__", ctype_get_pointer_type, ctype_set_pointer_type,
|
||||
"pointer type", NULL },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
static PyType_Slot ctype_type_slots[] = {
|
||||
{Py_tp_token, Py_TP_USE_SPEC},
|
||||
{Py_tp_traverse, CType_Type_traverse},
|
||||
{Py_tp_clear, CType_Type_clear},
|
||||
{Py_tp_dealloc, CType_Type_dealloc},
|
||||
{Py_tp_methods, ctype_methods},
|
||||
{Py_tp_getset, ctype_getsets},
|
||||
// Sequence protocol.
|
||||
{Py_sq_repeat, CType_Type_repeat},
|
||||
{0, NULL},
|
||||
|
@ -1181,7 +1238,7 @@ class _ctypes.PyCPointerType "PyObject *" "clinic_state()->PyCPointerType_Type"
|
|||
|
||||
|
||||
static int
|
||||
PyCPointerType_SetProto(ctypes_state *st, StgInfo *stginfo, PyObject *proto)
|
||||
PyCPointerType_SetProto(ctypes_state *st, PyObject *self, StgInfo *stginfo, PyObject *proto)
|
||||
{
|
||||
if (!proto || !PyType_Check(proto)) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
|
@ -1193,12 +1250,13 @@ PyCPointerType_SetProto(ctypes_state *st, StgInfo *stginfo, PyObject *proto)
|
|||
return -1;
|
||||
}
|
||||
if (!info) {
|
||||
PyErr_SetString(PyExc_TypeError,
|
||||
"_type_ must have storage info");
|
||||
PyErr_Format(PyExc_TypeError, "%R must have storage info", proto);
|
||||
return -1;
|
||||
}
|
||||
Py_INCREF(proto);
|
||||
Py_XSETREF(stginfo->proto, proto);
|
||||
Py_XSETREF(stginfo->proto, Py_NewRef(proto));
|
||||
if (info->pointer_type == NULL) {
|
||||
Py_XSETREF(info->pointer_type, Py_NewRef(self));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1251,7 +1309,7 @@ PyCPointerType_init(PyObject *self, PyObject *args, PyObject *kwds)
|
|||
}
|
||||
if (proto) {
|
||||
const char *current_format;
|
||||
if (PyCPointerType_SetProto(st, stginfo, proto) < 0) {
|
||||
if (PyCPointerType_SetProto(st, self, stginfo, proto) < 0) {
|
||||
Py_DECREF(proto);
|
||||
return -1;
|
||||
}
|
||||
|
@ -1309,10 +1367,9 @@ PyCPointerType_set_type_impl(PyTypeObject *self, PyTypeObject *cls,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (PyCPointerType_SetProto(st, info, type) < 0) {
|
||||
if (PyCPointerType_SetProto(st, (PyObject *)self, info, type) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (PyObject_SetAttr((PyObject *)self, &_Py_ID(_type_), type) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -6206,7 +6263,6 @@ _ctypes_add_objects(PyObject *mod)
|
|||
} while (0)
|
||||
|
||||
ctypes_state *st = get_module_state(mod);
|
||||
MOD_ADD("_pointer_type_cache", Py_NewRef(st->_ctypes_ptrtype_cache));
|
||||
|
||||
#ifdef MS_WIN32
|
||||
MOD_ADD("COMError", Py_NewRef(st->PyComError_Type));
|
||||
|
@ -6269,11 +6325,6 @@ _ctypes_mod_exec(PyObject *mod)
|
|||
return -1;
|
||||
}
|
||||
|
||||
st->_ctypes_ptrtype_cache = PyDict_New();
|
||||
if (st->_ctypes_ptrtype_cache == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
st->PyExc_ArgError = PyErr_NewException("ctypes.ArgumentError", NULL, NULL);
|
||||
if (!st->PyExc_ArgError) {
|
||||
return -1;
|
||||
|
@ -6312,7 +6363,6 @@ _ctypes_mod_exec(PyObject *mod)
|
|||
static int
|
||||
module_traverse(PyObject *module, visitproc visit, void *arg) {
|
||||
ctypes_state *st = get_module_state(module);
|
||||
Py_VISIT(st->_ctypes_ptrtype_cache);
|
||||
Py_VISIT(st->_unpickle);
|
||||
Py_VISIT(st->array_cache);
|
||||
Py_VISIT(st->error_object_name);
|
||||
|
@ -6347,7 +6397,6 @@ module_traverse(PyObject *module, visitproc visit, void *arg) {
|
|||
static int
|
||||
module_clear(PyObject *module) {
|
||||
ctypes_state *st = get_module_state(module);
|
||||
Py_CLEAR(st->_ctypes_ptrtype_cache);
|
||||
Py_CLEAR(st->_unpickle);
|
||||
Py_CLEAR(st->array_cache);
|
||||
Py_CLEAR(st->error_object_name);
|
||||
|
|
|
@ -1972,105 +1972,6 @@ error:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_ctypes.POINTER as create_pointer_type
|
||||
|
||||
type as cls: object
|
||||
A ctypes type.
|
||||
/
|
||||
|
||||
Create and return a new ctypes pointer type.
|
||||
|
||||
Pointer types are cached and reused internally,
|
||||
so calling this function repeatedly is cheap.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
create_pointer_type(PyObject *module, PyObject *cls)
|
||||
/*[clinic end generated code: output=98c3547ab6f4f40b input=3b81cff5ff9b9d5b]*/
|
||||
{
|
||||
PyObject *result;
|
||||
PyTypeObject *typ;
|
||||
PyObject *key;
|
||||
|
||||
assert(module);
|
||||
ctypes_state *st = get_module_state(module);
|
||||
if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, cls, &result) != 0) {
|
||||
// found or error
|
||||
return result;
|
||||
}
|
||||
// not found
|
||||
if (PyUnicode_CheckExact(cls)) {
|
||||
PyObject *name = PyUnicode_FromFormat("LP_%U", cls);
|
||||
result = PyObject_CallFunction((PyObject *)Py_TYPE(st->PyCPointer_Type),
|
||||
"N(O){}",
|
||||
name,
|
||||
st->PyCPointer_Type);
|
||||
if (result == NULL)
|
||||
return result;
|
||||
key = PyLong_FromVoidPtr(result);
|
||||
if (key == NULL) {
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
} else if (PyType_Check(cls)) {
|
||||
typ = (PyTypeObject *)cls;
|
||||
PyObject *name = PyUnicode_FromFormat("LP_%s", typ->tp_name);
|
||||
result = PyObject_CallFunction((PyObject *)Py_TYPE(st->PyCPointer_Type),
|
||||
"N(O){sO}",
|
||||
name,
|
||||
st->PyCPointer_Type,
|
||||
"_type_", cls);
|
||||
if (result == NULL)
|
||||
return result;
|
||||
key = Py_NewRef(cls);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "must be a ctypes type");
|
||||
return NULL;
|
||||
}
|
||||
if (PyDict_SetItem(st->_ctypes_ptrtype_cache, key, result) < 0) {
|
||||
Py_DECREF(result);
|
||||
Py_DECREF(key);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
_ctypes.pointer as create_pointer_inst
|
||||
|
||||
obj as arg: object
|
||||
/
|
||||
|
||||
Create a new pointer instance, pointing to 'obj'.
|
||||
|
||||
The returned object is of the type POINTER(type(obj)). Note that if you
|
||||
just want to pass a pointer to an object to a foreign function call, you
|
||||
should use byref(obj) which is much faster.
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
create_pointer_inst(PyObject *module, PyObject *arg)
|
||||
/*[clinic end generated code: output=3b543bc9f0de2180 input=713685fdb4d9bc27]*/
|
||||
{
|
||||
PyObject *result;
|
||||
PyObject *typ;
|
||||
|
||||
ctypes_state *st = get_module_state(module);
|
||||
if (PyDict_GetItemRef(st->_ctypes_ptrtype_cache, (PyObject *)Py_TYPE(arg), &typ) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if (typ == NULL) {
|
||||
typ = create_pointer_type(module, (PyObject *)Py_TYPE(arg));
|
||||
if (typ == NULL)
|
||||
return NULL;
|
||||
}
|
||||
result = PyObject_CallOneArg(typ, arg);
|
||||
Py_DECREF(typ);
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
buffer_info(PyObject *self, PyObject *arg)
|
||||
{
|
||||
|
@ -2105,8 +2006,6 @@ buffer_info(PyObject *self, PyObject *arg)
|
|||
PyMethodDef _ctypes_module_methods[] = {
|
||||
{"get_errno", get_errno, METH_NOARGS},
|
||||
{"set_errno", set_errno, METH_VARARGS},
|
||||
CREATE_POINTER_TYPE_METHODDEF
|
||||
CREATE_POINTER_INST_METHODDEF
|
||||
{"_unpickle", unpickle, METH_VARARGS },
|
||||
{"buffer_info", buffer_info, METH_O, "Return buffer interface information"},
|
||||
_CTYPES_RESIZE_METHODDEF
|
||||
|
|
30
Modules/_ctypes/clinic/callproc.c.h
generated
30
Modules/_ctypes/clinic/callproc.c.h
generated
|
@ -142,32 +142,4 @@ _ctypes_resize(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
|
|||
exit:
|
||||
return return_value;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(create_pointer_type__doc__,
|
||||
"POINTER($module, type, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Create and return a new ctypes pointer type.\n"
|
||||
"\n"
|
||||
" type\n"
|
||||
" A ctypes type.\n"
|
||||
"\n"
|
||||
"Pointer types are cached and reused internally,\n"
|
||||
"so calling this function repeatedly is cheap.");
|
||||
|
||||
#define CREATE_POINTER_TYPE_METHODDEF \
|
||||
{"POINTER", (PyCFunction)create_pointer_type, METH_O, create_pointer_type__doc__},
|
||||
|
||||
PyDoc_STRVAR(create_pointer_inst__doc__,
|
||||
"pointer($module, obj, /)\n"
|
||||
"--\n"
|
||||
"\n"
|
||||
"Create a new pointer instance, pointing to \'obj\'.\n"
|
||||
"\n"
|
||||
"The returned object is of the type POINTER(type(obj)). Note that if you\n"
|
||||
"just want to pass a pointer to an object to a foreign function call, you\n"
|
||||
"should use byref(obj) which is much faster.");
|
||||
|
||||
#define CREATE_POINTER_INST_METHODDEF \
|
||||
{"pointer", (PyCFunction)create_pointer_inst, METH_O, create_pointer_inst__doc__},
|
||||
/*[clinic end generated code: output=46a3841cbe5ddc96 input=a9049054013a1b77]*/
|
||||
/*[clinic end generated code: output=23c74aced603977d input=a9049054013a1b77]*/
|
||||
|
|
|
@ -83,8 +83,6 @@ typedef struct {
|
|||
#ifdef MS_WIN32
|
||||
PyTypeObject *PyComError_Type;
|
||||
#endif
|
||||
/* This dict maps ctypes types to POINTER types */
|
||||
PyObject *_ctypes_ptrtype_cache;
|
||||
/* a callable object used for unpickling:
|
||||
strong reference to _ctypes._unpickle() function */
|
||||
PyObject *_unpickle;
|
||||
|
@ -390,6 +388,8 @@ typedef struct {
|
|||
PyObject *converters; /* tuple([t.from_param for t in argtypes]) */
|
||||
PyObject *restype; /* CDataObject or NULL */
|
||||
PyObject *checker;
|
||||
PyObject *pointer_type; /* __pointer_type__ attribute;
|
||||
arbitrary object or NULL */
|
||||
PyObject *module;
|
||||
int flags; /* calling convention and such */
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
@ -452,6 +452,7 @@ stginfo_set_dict_final(StgInfo *info)
|
|||
|
||||
extern int PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info);
|
||||
extern void ctype_clear_stginfo(StgInfo *info);
|
||||
extern void ctype_free_stginfo_members(StgInfo *info);
|
||||
|
||||
typedef int(* PPROC)(void);
|
||||
|
||||
|
@ -640,6 +641,7 @@ PyStgInfo_Init(ctypes_state *state, PyTypeObject *type)
|
|||
if (!module) {
|
||||
return NULL;
|
||||
}
|
||||
info->pointer_type = NULL;
|
||||
info->module = Py_NewRef(module);
|
||||
|
||||
info->initialized = 1;
|
||||
|
|
|
@ -25,13 +25,7 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
|
|||
{
|
||||
Py_ssize_t size;
|
||||
|
||||
ctype_clear_stginfo(dst_info);
|
||||
PyMem_Free(dst_info->ffi_type_pointer.elements);
|
||||
PyMem_Free(dst_info->format);
|
||||
dst_info->format = NULL;
|
||||
PyMem_Free(dst_info->shape);
|
||||
dst_info->shape = NULL;
|
||||
dst_info->ffi_type_pointer.elements = NULL;
|
||||
ctype_free_stginfo_members(dst_info);
|
||||
|
||||
memcpy(dst_info, src_info, sizeof(StgInfo));
|
||||
#ifdef Py_GIL_DISABLED
|
||||
|
@ -45,6 +39,7 @@ PyCStgInfo_clone(StgInfo *dst_info, StgInfo *src_info)
|
|||
Py_XINCREF(dst_info->restype);
|
||||
Py_XINCREF(dst_info->checker);
|
||||
Py_XINCREF(dst_info->module);
|
||||
dst_info->pointer_type = NULL; // the cache cannot be shared
|
||||
|
||||
if (src_info->format) {
|
||||
dst_info->format = PyMem_Malloc(strlen(src_info->format) + 1);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue