Skip to content

Commit 9e21b07

Browse files
committed
gh-148596: Fix callback return handling for subclassed ctypes types
1 parent 7ce737e commit 9e21b07

File tree

2 files changed

+24
-1
lines changed

2 files changed

+24
-1
lines changed

Lib/test/test_ctypes/test_callbacks.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,17 @@ def func():
328328
f"of ctypes callback function {func!r}")
329329
self.assertIsNone(cm.unraisable.object)
330330

331+
def test_callback_return_subclass(self):
332+
class MyInt(ctypes.c_int):
333+
pass
334+
335+
@ctypes.CFUNCTYPE(MyInt, MyInt)
336+
def identity(x):
337+
return x
338+
339+
result = identity(MyInt(42))
340+
assert isinstance(result, MyInt)
341+
assert result.value == 42
331342

332343
if __name__ == '__main__':
333344
unittest.main()

Modules/_ctypes/callbacks.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,19 @@ static void _CallPythonObject(ctypes_state *st,
239239
be the result. EXCEPT when restype is py_object - Python
240240
itself knows how to manage the refcount of these objects.
241241
*/
242-
PyObject *keep = setfunc(mem, result, restype->size);
242+
PyObject *value = result; /* borrowed */
243+
PyObject *unwrapped = NULL; /* new ref if created */
244+
if (value != NULL && PyObject_TypeCheck(value, st->PyCData_Type)) {
245+
unwrapped = PyObject_GetAttrString(value, "value");
246+
if (unwrapped != NULL) {
247+
value = unwrapped;
248+
} else {
249+
/* fallback: clear error and keep original */
250+
PyErr_Clear();
251+
}
252+
}
253+
254+
PyObject *keep = setfunc(mem, value, restype->size);
243255

244256
if (keep == NULL) {
245257
/* Could not convert callback result. */

0 commit comments

Comments
 (0)