Skip to content
40 changes: 40 additions & 0 deletions Doc/library/ctypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -694,6 +694,46 @@ through the :attr:`~_CFuncPtr.errcheck` attribute;
see the reference manual for details.


Specifying function pointers using type annotations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. decorator:: wrap_dll_function(dll)
:module: ctypes.util

A :term:`decorator` that generates :attr:`~ctypes._CFuncPtr.argtypes` and
:attr:`~ctypes._CFuncPtr.restype` from a function signature, using the
:attr:`~function.__name__` of the function and its :term:`type annotations <annotation>`.

The decorated function should look like this::

@wrap_dll_function(dll_to_wrap)
def function_ptr_name(arg_name: ctypes_type, ...) -> ctypes_type:
# There should be no body
pass
Comment thread
johnslavik marked this conversation as resolved.

The body of the decorated function is ignored, and any parameters that are
missing type annotations are skipped. The names of the parameters are ignored
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.

For example::

import ctypes
from ctypes.util import wrap_dll_function

@wrap_dll_function(ctypes.pythonapi)
def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p) -> ctypes.py_object:
pass

PyObject_GetAttrString(42, b"real")


.. versionadded:: next


.. _ctypes-passing-pointers:

Passing pointers (or: passing parameters by reference)
Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ ctypes
from an annotation-based syntax, similar to how the :mod:`dataclasses` module
is used.
(Contributed by Peter Bierma in :gh:`104533`.)
* Add :func:`ctypes.util.wrap_dll_function` for generating function pointers
through a function signature.
(Contributed by Peter Bierma in :gh:`153903`.)


encodings
Expand Down
18 changes: 18 additions & 0 deletions Lib/ctypes/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,24 @@ def inner(decorated_class):

return process_the_struct(class_or_none)


def wrap_dll_function(dll):
def decorator(func):
name = func.__name__
ptr = getattr(dll, name)
annotations = annotationlib.get_annotations(func)

try:
restype = annotations.pop("return")
except KeyError as error:
raise ValueError(f"{name!r} missing return type annotation") from error

ptr.restype = restype
ptr.argtypes = tuple(annotations.values())
return ptr

return decorator

################################################################
# test code

Expand Down
21 changes: 21 additions & 0 deletions Lib/test/test_ctypes/test_funcptr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import unittest
from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr,
c_void_p, c_char_p, c_char, c_int, c_uint, c_long)
from ctypes.util import wrap_dll_function
from test.support import import_helper
_ctypes_test = import_helper.import_module("_ctypes_test")
from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
Expand Down Expand Up @@ -130,6 +131,26 @@ def c_string(init):
def test_abstract(self):
self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid")

def test_wrap_dll_function(self):
@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")

with self.assertRaises(AttributeError):
@wrap_dll_function(ctypes.pythonapi)
def noexist():
pass

with self.assertRaisesRegex(ValueError, "'PyObject_GetAttrString' missing return type annotation"):
@wrap_dll_function(ctypes.pythonapi)
def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p):
pass


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :func:`ctypes.util.wrap_dll_function` for creating
:class:`~ctypes._CFuncPtr` objects from a function signature.
Loading