Skip to content

Commit 0e81ef9

Browse files
ZeroIntensitytomasr8johnslavik
authored
gh-153903: Add a ctypes decorator for generating function pointers using annotations (GH-153904)
Co-authored-by: Tomas R. <tomas.roun8@gmail.com> Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
1 parent d12434b commit 0e81ef9

5 files changed

Lines changed: 84 additions & 0 deletions

File tree

Doc/library/ctypes.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,46 @@ through the :attr:`~_CFuncPtr.errcheck` attribute;
694694
see the reference manual for details.
695695

696696

697+
Specifying function pointers using type annotations
698+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
699+
700+
.. decorator:: wrap_dll_function(dll)
701+
:module: ctypes.util
702+
703+
A :term:`decorator` that generates :attr:`~ctypes._CFuncPtr.argtypes` and
704+
:attr:`~ctypes._CFuncPtr.restype` from a function signature, using the
705+
:attr:`~function.__name__` of the function and its :term:`type annotations <annotation>`.
706+
707+
The decorated function should look like this::
708+
709+
@wrap_dll_function(dll_to_wrap)
710+
def function_ptr_name(arg_name: ctypes_type, ...) -> ctypes_type:
711+
# There should be no body
712+
pass
713+
714+
The body of the decorated function is ignored, and any parameters that are
715+
missing type annotations are skipped. The names of the parameters are ignored
716+
and do not have to match the underlying C implementation.
717+
718+
If the decorated function does not have a return type annotation, a
719+
:exc:`ValueError` is raised. If the name of the function does not exist
720+
in *dll*, an :exc:`AttributeError` is raised.
721+
722+
For example::
723+
724+
import ctypes
725+
from ctypes.util import wrap_dll_function
726+
727+
@wrap_dll_function(ctypes.pythonapi)
728+
def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p) -> ctypes.py_object:
729+
pass
730+
731+
PyObject_GetAttrString(42, b"real")
732+
733+
734+
.. versionadded:: next
735+
736+
697737
.. _ctypes-passing-pointers:
698738

699739
Passing pointers (or: passing parameters by reference)

Doc/whatsnew/3.16.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ ctypes
237237
from an annotation-based syntax, similar to how the :mod:`dataclasses` module
238238
is used.
239239
(Contributed by Peter Bierma in :gh:`104533`.)
240+
* Add :func:`ctypes.util.wrap_dll_function` for generating function pointers
241+
through a function signature.
242+
(Contributed by Peter Bierma in :gh:`153903`.)
240243

241244

242245
encodings

Lib/ctypes/util.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,24 @@ def inner(decorated_class):
574574

575575
return process_the_struct(class_or_none)
576576

577+
578+
def wrap_dll_function(dll):
579+
def decorator(func):
580+
name = func.__name__
581+
ptr = getattr(dll, name)
582+
annotations = annotationlib.get_annotations(func)
583+
584+
try:
585+
restype = annotations.pop("return")
586+
except KeyError as error:
587+
raise ValueError(f"{name!r} missing return type annotation") from error
588+
589+
ptr.restype = restype
590+
ptr.argtypes = tuple(annotations.values())
591+
return ptr
592+
593+
return decorator
594+
577595
################################################################
578596
# test code
579597

Lib/test/test_ctypes/test_funcptr.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import unittest
33
from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr,
44
c_void_p, c_char_p, c_char, c_int, c_uint, c_long)
5+
from ctypes.util import wrap_dll_function
56
from test.support import import_helper
67
_ctypes_test = import_helper.import_module("_ctypes_test")
78
from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
@@ -130,6 +131,26 @@ def c_string(init):
130131
def test_abstract(self):
131132
self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid")
132133

134+
def test_wrap_dll_function(self):
135+
@wrap_dll_function(ctypes.pythonapi)
136+
def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object) -> ctypes.py_object:
137+
pass
138+
139+
class Foo:
140+
a = "abc"
141+
142+
self.assertEqual(PyObject_GetAttr(Foo, "a"), "abc")
143+
144+
with self.assertRaises(AttributeError):
145+
@wrap_dll_function(ctypes.pythonapi)
146+
def noexist():
147+
pass
148+
149+
with self.assertRaisesRegex(ValueError, "'PyObject_GetAttrString' missing return type annotation"):
150+
@wrap_dll_function(ctypes.pythonapi)
151+
def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p):
152+
pass
153+
133154

134155
if __name__ == '__main__':
135156
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :func:`ctypes.util.wrap_dll_function` for creating
2+
:class:`~ctypes._CFuncPtr` objects from a function signature.

0 commit comments

Comments
 (0)