diff --git a/src/rubicon/objc/ctypes_patch.py b/src/rubicon/objc/ctypes_patch.py index 2444a2a2..d3eea426 100644 --- a/src/rubicon/objc/ctypes_patch.py +++ b/src/rubicon/objc/ctypes_patch.py @@ -81,12 +81,17 @@ class ffi_type(ctypes.Structure): # The GETFUNC and SETFUNC typedefs from "Modules/_ctypes/ctypes.h". GETFUNC = ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p, ctypes.c_ssize_t) -# The return type of SETFUNC is declared here as a c_void_p instead of py_object to work -# around a ctypes bug (https://github.com/python/cpython/issues/81061). See the comment -# in make_callback_returnable's setfunc for details. -SETFUNC = ctypes.PYFUNCTYPE( - ctypes.c_void_p, ctypes.c_void_p, ctypes.py_object, ctypes.c_ssize_t -) +if sys.version_info < (3, 10): + # The return type of SETFUNC is declared here as a c_void_p instead of py_object to work + # around a ctypes bug (https://github.com/python/cpython/issues/81061). See the comment + # in make_callback_returnable's setfunc for details. This bug was fixed in 3.10. + SETFUNC = ctypes.PYFUNCTYPE( + ctypes.c_void_p, ctypes.c_void_p, ctypes.py_object, ctypes.c_ssize_t + ) +else: + SETFUNC = ctypes.PYFUNCTYPE( + ctypes.py_object, ctypes.c_void_p, ctypes.py_object, ctypes.c_ssize_t + ) if sys.version_info < (3, 13): @@ -287,20 +292,22 @@ def setfunc(ptr, value, size): ) ctypes.memmove(ptr, ctypes.addressof(value), actual_size) - # Because of a ctypes bug (https://github.com/python/cpython/issues/81061), - # returning None from a callback with restype py_object causes a reference - # counting error that can crash Python. To work around this bug, the restype of - # SETFUNC is declared as c_void_p instead. This way ctypes performs no automatic - # reference counting for the returned object, which avoids the bug. However, - # this way we have to manually convert the Python object to a pointer and adjust - # its reference count. - none_ptr = ctypes.cast(id(None), ctypes.POINTER(PyObject)) - # The return value of a SETFUNC is expected to have an extra reference - # (which will be owned by the caller of the SETFUNC). - ctypes.pythonapi.Py_IncRef(none_ptr) - # The returned pointer must be returned as a plain int, not as a c_void_p, - # otherwise ctypes won't recognize it and will raise a TypeError. - return ctypes.cast(none_ptr, ctypes.c_void_p).value + + if sys.version_info < (3, 10): + # Because of a ctypes bug (https://github.com/python/cpython/issues/81061), + # returning None from a callback with restype py_object causes a reference + # counting error that can crash Python. To work around this bug, the restype of + # SETFUNC is declared as c_void_p instead. This way ctypes performs no automatic + # reference counting for the returned object, which avoids the bug. However, + # this way we have to manually convert the Python object to a pointer and adjust + # its reference count. This bug was fixed in 3.10. + none_ptr = ctypes.cast(id(None), ctypes.POINTER(PyObject)) + # The return value of a SETFUNC is expected to have an extra reference + # (which will be owned by the caller of the SETFUNC). + ctypes.pythonapi.Py_IncRef(none_ptr) + # The returned pointer must be returned as a plain int, not as a c_void_p, + # otherwise ctypes won't recognize it and will raise a TypeError. + return ctypes.cast(none_ptr, ctypes.c_void_p).value # Store the getfunc and setfunc as attributes on the ctype, so they don't # get garbage-collected.