Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,17 @@ In addition, if a function argument is explicitly declared to be a pointer type
type (``c_int`` in this case) can be passed to the function. ctypes will apply
the required :func:`byref` conversion in this case automatically.

These conversions are also applied to the :attr:`!_as_parameter_` attribute of
an object passed for a pointer argument (see
:ref:`ctypes-calling-functions-with-own-custom-data-types`). So, for a
``POINTER(c_int)`` argument, an object whose :attr:`!_as_parameter_` is a
``c_int`` instance is passed by reference, and one whose :attr:`!_as_parameter_`
is a ``c_int`` array is accepted as well.

.. versionchanged:: next
Objects with an :attr:`!_as_parameter_` attribute now receive the same
implicit conversions for pointer arguments as the object they wrap.

To set a POINTER type field to ``NULL``, you can assign ``None``::

>>> bar.values = None
Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ ctypes
They are only available with libffi 3.5 or later.
(Contributed by Serhiy Storchaka in :gh:`157487`.)

* Objects with an :attr:`!_as_parameter_` attribute are now
implicitly converted when passed for a :func:`~ctypes.POINTER` argument.
(Contributed by Peter Bierma in :gh:`71990`.)


curses
------
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_ctypes/test_as_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ class A:
ctypes.c_void_p,
ctypes.c_int, # PyCSimpleType
POINT, # CDataType
POINTER(c_int), # PyCPointerType
):
with self.subTest(c_type=c_type):
with self.assertRaises(RecursionError):
Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_ctypes/test_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
c_long, c_ulong,
c_longlong, c_ulonglong,
c_float, c_double, c_longdouble)
from ctypes.util import struct
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")

Expand Down Expand Up @@ -168,6 +169,71 @@ def test_array_pointers(self):
self.assertRaises(TypeError, LPINT.from_param, c_long*3)
self.assertRaises(TypeError, LPINT.from_param, c_uint*3)

def test_pointer_as_parameter(self):
class Wrapper:
def __init__(self, param):
self._as_parameter_ = param

p_int = POINTER(c_int)

# Implicit byref() of a wrapped instance
v = c_int(42)
x = p_int.from_param(Wrapper(v))
self.assertIs(x._obj, v)

# Wrapped pointer
p = pointer(c_int(99))
self.assertIs(p_int.from_param(Wrapper(p)), p)

# Wrapped byref()
p_int.from_param(Wrapper(byref(c_int(7))))

# Wrapped array
a = (c_int * 3)()
self.assertIs(p_int.from_param(Wrapper(a)), a)

@struct
class Base:
x: c_int

class Derived(Base):
pass

p_base = POINTER(Base)

# Pointer upcast of a wrapped pointer and a wrapped instance.
d = Derived()
pd = pointer(d)
self.assertIs(p_base.from_param(Wrapper(pd)), pd)
self.assertIs(p_base.from_param(Wrapper(d))._obj, d)

# Nested _as_parameter_.
self.assertIs(p_int.from_param(Wrapper(Wrapper(Wrapper(v))))._obj, v)

# None is still accepted.
self.assertIsNone(p_int.from_param(Wrapper(None)))

# Type mismatches are still rejected.
with self.assertRaises(TypeError):
p_int.from_param(Wrapper(c_short(1)))
with self.assertRaises(TypeError):
p_int.from_param(Wrapper(pointer(c_short(1))))
with self.assertRaises(TypeError):
p_int.from_param(Wrapper(byref(c_short(1))))
with self.assertRaises(TypeError):
p_int.from_param(Wrapper((c_short * 3)()))
with self.assertRaises(TypeError):
p_int.from_param(Wrapper(object()))

# End-to-end through a function call. The c_int must stay alive
# while the returned pointer is used.
func = CDLL(_ctypes_test.__file__)._testfunc_p_p
func.restype = p_int
func.argtypes = [p_int]
v = c_int(123)
result = func(Wrapper(v))
self.assertEqual(result.contents.value, 123)

def test_noctypes_argtype(self):
func = CDLL(_ctypes_test.__file__)._testfunc_p_p
func.restype = c_void_p
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:mod:`ctypes`: objects with an :attr:`!_as_parameter_` attribute are now
implicitly converted when passed for a :func:`~ctypes.POINTER` argument.
15 changes: 15 additions & 0 deletions Modules/_ctypes/_ctypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,21 @@ PyCPointerType_from_param_impl(PyObject *type, PyTypeObject *cls,
return Py_NewRef(value);
}
}

PyObject *as_parameter;
if (PyObject_GetOptionalAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) {
return NULL;
}
if (as_parameter) {
if (_Py_EnterRecursiveCall(" while processing _as_parameter_")) {
Py_DECREF(as_parameter);
return NULL;
}
value = PyCPointerType_from_param_impl(type, cls, as_parameter);
Py_DECREF(as_parameter);
_Py_LeaveRecursiveCall();
return value;
}
return CDataType_from_param_impl(type, cls, value);
}

Expand Down
Loading