diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index 50ab2937562..4de5c820f2c 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -466,6 +466,14 @@ integer, string, bytes, a :mod:`ctypes` instance, or an object with an Return types ^^^^^^^^^^^^ +.. testsetup:: + + from ctypes import CDLL, c_char, c_char_p + from ctypes.util import find_library + libc = CDLL(find_library('c')) + strchr = libc.strchr + + By default functions are assumed to return the C :c:expr:`int` type. Other return types can be specified by setting the :attr:`restype` attribute of the function object. @@ -502,18 +510,19 @@ If you want to avoid the ``ord("x")`` calls above, you can set the :attr:`argtypes` attribute, and the second argument will be converted from a single character Python bytes object into a C char:: +.. doctest:: + >>> strchr.restype = c_char_p >>> strchr.argtypes = [c_char_p, c_char] >>> strchr(b"abcdef", b"d") - 'def' + b'def' >>> strchr(b"abcdef", b"def") Traceback (most recent call last): - File "", line 1, in - ArgumentError: argument 2: TypeError: one character string expected + ctypes.ArgumentError: argument 2: TypeError: one character bytes, bytearray or integer expected >>> print(strchr(b"abcdef", b"x")) None >>> strchr(b"abcdef", b"d") - 'def' + b'def' >>> You can also use a callable Python object (a function or a class for example) as diff --git a/Lib/test/test_ctypes/test_functions.py b/Lib/test/test_ctypes/test_functions.py index 95633dfa8b3..703bd2c601c 100644 --- a/Lib/test/test_ctypes/test_functions.py +++ b/Lib/test/test_ctypes/test_functions.py @@ -54,6 +54,23 @@ class FunctionTestCase(unittest.TestCase): class X(object, Structure): _fields_ = [] + def test_c_char_parm(self): + proto = CFUNCTYPE(c_int, c_char) + def callback(*args): + return 0 + + callback = proto(callback) + + self.assertEqual(callback(b"a"), 0) + + with self.assertRaises(ArgumentError) as cm: + callback(b"abc") + + self.assertEqual(str(cm.exception), + "argument 1: TypeError: one character bytes, " + "bytearray or integer expected") + + @need_symbol('c_wchar') def test_wchar_parm(self): f = dll._testfunc_i_bhilfd @@ -62,6 +79,18 @@ class FunctionTestCase(unittest.TestCase): self.assertEqual(result, 139) self.assertEqual(type(result), int) + with self.assertRaises(ArgumentError) as cm: + f(1, 2, 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: unicode string expected " + "instead of int instance") + + with self.assertRaises(ArgumentError) as cm: + f(1, "abc", 3, 4, 5.0, 6.0) + self.assertEqual(str(cm.exception), + "argument 2: TypeError: one character unicode string " + "expected") + @need_symbol('c_wchar') def test_wchar_result(self): f = dll._testfunc_i_bhilfd diff --git a/Lib/test/test_ctypes/test_parameters.py b/Lib/test/test_ctypes/test_parameters.py index 84839d9c6a9..22d290db1bc 100644 --- a/Lib/test/test_ctypes/test_parameters.py +++ b/Lib/test/test_ctypes/test_parameters.py @@ -78,6 +78,29 @@ class SimpleTypesTestCase(unittest.TestCase): pa = c_wchar_p.from_param(c_wchar_p("123")) self.assertEqual(type(pa), c_wchar_p) + def test_c_char(self): + from ctypes import c_char + + with self.assertRaises(TypeError) as cm: + c_char.from_param(b"abc") + self.assertEqual(str(cm.exception), + "one character bytes, bytearray or integer expected") + + @need_symbol('c_wchar') + def test_c_wchar(self): + from ctypes import c_wchar + + with self.assertRaises(TypeError) as cm: + c_wchar.from_param("abc") + self.assertEqual(str(cm.exception), + "one character unicode string expected") + + + with self.assertRaises(TypeError) as cm: + c_wchar.from_param(123) + self.assertEqual(str(cm.exception), + "unicode string expected instead of int instance") + def test_int_pointers(self): from ctypes import c_short, c_uint, c_int, c_long, POINTER, pointer LPINT = POINTER(c_int) diff --git a/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst b/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst new file mode 100644 index 00000000000..97e9569e40a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-11-24-21-52-31.gh-issue-99266.88GcV9.rst @@ -0,0 +1 @@ +Preserve more detailed error messages in :mod:`ctypes`. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 4ce6433a2e4..272cafb5a9a 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -2197,6 +2197,7 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) struct fielddesc *fd; PyObject *as_parameter; int res; + PyObject *exc, *val, *tb; /* If the value is already an instance of the requested type, we can use it as is */ @@ -2230,24 +2231,37 @@ PyCSimpleType_from_param(PyObject *type, PyObject *value) parg->obj = fd->setfunc(&parg->value, value, 0); if (parg->obj) return (PyObject *)parg; - PyErr_Clear(); + PyErr_Fetch(&exc, &val, &tb); Py_DECREF(parg); if (_PyObject_LookupAttr(value, &_Py_ID(_as_parameter_), &as_parameter) < 0) { + Py_XDECREF(exc); + Py_XDECREF(val); + Py_XDECREF(tb); return NULL; } if (as_parameter) { if (_Py_EnterRecursiveCall("while processing _as_parameter_")) { Py_DECREF(as_parameter); + Py_XDECREF(exc); + Py_XDECREF(val); + Py_XDECREF(tb); return NULL; } value = PyCSimpleType_from_param(type, as_parameter); _Py_LeaveRecursiveCall(); Py_DECREF(as_parameter); + Py_XDECREF(exc); + Py_XDECREF(val); + Py_XDECREF(tb); return value; } - PyErr_SetString(PyExc_TypeError, - "wrong type"); + if (exc) { + PyErr_Restore(exc, val, tb); + } + else { + PyErr_SetString(PyExc_TypeError, "wrong type"); + } return NULL; }