gh-156500: Reject non-positional parameters in `ctypes.util.wrap_dll_function` (GH-156501)

argtypes was built from every annotated parameter, so an annotated
keyword-only, *args, or **kwargs parameter contributed an extra positional
entry. Such a parameter has no positional counterpart to describe, so it
now raises ValueError at decoration time.
diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index acd6c2f..d76c449 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -715,8 +715,11 @@
    and do not have to match the underlying C implementation.
 
    If the decorated function does not have a return type annotation, a
-   :exc:`ValueError` is raised. If the name of the function does not exist
-   in *dll*, an :exc:`AttributeError` is raised.
+   :exc:`ValueError` is raised. A :exc:`ValueError` is also raised if it has a
+   keyword-only, ``*args``, or ``**kwargs`` parameter, since
+   :attr:`~ctypes._CFuncPtr.argtypes` describes positional arguments only. If
+   the name of the function does not exist in *dll*, an :exc:`AttributeError`
+   is raised.
 
    For example::
 
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 141d3fb..2b01506 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -4,6 +4,7 @@
 from dataclasses import dataclass
 
 lazy import functools
+lazy import inspect
 lazy import shutil
 lazy import subprocess
 
@@ -509,6 +510,13 @@ def decorator(func):
         except KeyError as error:
             raise ValueError(f"{name!r} missing return type annotation") from error
 
+        for param in inspect.signature(func).parameters.values():
+            if param.kind not in (param.POSITIONAL_ONLY,
+                                  param.POSITIONAL_OR_KEYWORD):
+                raise ValueError(f"{name!r} has non-positional parameter "
+                                 f"{param.name!r}; argtypes describes "
+                                 f"positional arguments only")
+
         ptr.restype = restype
         ptr.argtypes = tuple(annotations.values())
         functools.update_wrapper(ptr, func, updated=())
diff --git a/Lib/test/test_ctypes/test_funcptr.py b/Lib/test/test_ctypes/test_funcptr.py
index 28ff34f..b4c2780 100644
--- a/Lib/test/test_ctypes/test_funcptr.py
+++ b/Lib/test/test_ctypes/test_funcptr.py
@@ -153,6 +153,47 @@ def noexist():
             def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p):
                 pass
 
+    def test_wrap_dll_function_non_positional(self):
+        # argtypes describes positional arguments only, so a parameter that
+        # cannot be passed positionally is rejected.
+        regex = "'PyObject_GetAttr' has non-positional parameter"
+
+        with self.assertRaisesRegex(ValueError, regex):
+            @wrap_dll_function(ctypes.pythonapi)
+            def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+                                 *args: ctypes.c_int) -> ctypes.py_object:
+                pass
+
+        with self.assertRaisesRegex(ValueError, regex):
+            @wrap_dll_function(ctypes.pythonapi)
+            def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+                                 **kwargs: ctypes.c_int) -> ctypes.py_object:
+                pass
+
+        with self.assertRaisesRegex(ValueError, regex):
+            @wrap_dll_function(ctypes.pythonapi)
+            def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+                                 *, kwonly: ctypes.c_int) -> ctypes.py_object:
+                pass
+
+        with self.assertRaisesRegex(ValueError, regex):
+            @wrap_dll_function(ctypes.pythonapi)
+            def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+                                 *, kwonly) -> ctypes.py_object:
+                pass
+
+        # Positional-only parameters have a positional counterpart, so they
+        # are accepted.
+        @wrap_dll_function(ctypes.pythonapi)
+        def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+                             /) -> ctypes.py_object:
+            pass
+
+        class Foo:
+            a = "abc"
+
+        self.assertEqual(PyObject_GetAttr(Foo, "a"), "abc")
+
     def test_wrap_dll_function_str_ann(self):
         from test.test_ctypes import wrap_str_ann
         version = wrap_str_ann.Py_GetVersion()