https://github.com/python/cpython/commit/3758955e2fb844cd764a59ba8519c916877dd8f5
commit: 3758955e2fb844cd764a59ba8519c916877dd8f5
branch: main
author: Peter Bierma <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-10-20T12:55:15-04:00
summary:

gh-71990: Call `_as_parameter_` on `ctypes.POINTER` arguments (GH-159763)

files:
A Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
M Doc/library/ctypes.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_ctypes/test_as_parameter.py
M Lib/test/test_ctypes/test_parameters.py
M Modules/_ctypes/_ctypes.c

diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index bafe00a6a95703..311ab65e802310 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -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
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 565cd7504d7ea9..58f129bedcb371 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -214,6 +214,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
 ------
diff --git a/Lib/test/test_ctypes/test_as_parameter.py 
b/Lib/test/test_ctypes/test_as_parameter.py
index abbfb6efdcea55..8634c3f03a02ec 100644
--- a/Lib/test/test_ctypes/test_as_parameter.py
+++ b/Lib/test/test_ctypes/test_as_parameter.py
@@ -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):
diff --git a/Lib/test/test_ctypes/test_parameters.py 
b/Lib/test/test_ctypes/test_parameters.py
index 6dadb7b410d703..ba3de0e5908494 100644
--- a/Lib/test/test_ctypes/test_parameters.py
+++ b/Lib/test/test_ctypes/test_parameters.py
@@ -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")
 
@@ -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
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst 
b/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
new file mode 100644
index 00000000000000..87276db65054a0
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-19-16-26-57.gh-issue-71990.K3FlJ9.rst
@@ -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.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 67e4835ea28bdb..605925a8eaaae7 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -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);
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to