diff -r 06cf4044a11a Doc/library/ctypes.rst --- a/Doc/library/ctypes.rst Sat Aug 09 09:34:25 2014 +0300 +++ b/Doc/library/ctypes.rst Sat Aug 09 10:00:17 2014 +0300 @@ -202,9 +202,9 @@ debugging crashes (e.g. from segmentation faults produced by erroneous C library calls). -``None``, integers, bytes objects and (unicode) strings are the only native +``None``, integers, bytes objects, bytearrays and (unicode) strings are the only native Python objects that can directly be used as parameters in these function calls. -``None`` is passed as a C ``NULL`` pointer, bytes objects and strings are passed +``None`` is passed as a C ``NULL`` pointer, bytes objects, bytearrays and strings are passed as pointer to the memory block that contains their data (:c:type:`char *` or :c:type:`wchar_t *`). Python integers are passed as the platforms default C :c:type:`int` type, their value is masked to fit into the C type. @@ -226,6 +226,7 @@ | :class:`c_bool` | :c:type:`_Bool` | bool (1) | +----------------------+------------------------------------------+----------------------------+ | :class:`c_char` | :c:type:`char` | 1-character bytes object | +| | | or bytearray | +----------------------+------------------------------------------+----------------------------+ | :class:`c_wchar` | :c:type:`wchar_t` | 1-character string | +----------------------+------------------------------------------+----------------------------+ @@ -261,7 +262,8 @@ +----------------------+------------------------------------------+----------------------------+ | :class:`c_longdouble`| :c:type:`long double` | float | +----------------------+------------------------------------------+----------------------------+ -| :class:`c_char_p` | :c:type:`char *` (NUL terminated) | bytes object or ``None`` | +| :class:`c_char_p` | :c:type:`char *` (NUL terminated) | bytes object, bytearray or | +| | | ``None`` | +----------------------+------------------------------------------+----------------------------+ | :class:`c_wchar_p` | :c:type:`wchar_t *` (NUL terminated) | string or ``None`` | +----------------------+------------------------------------------+----------------------------+ @@ -366,9 +368,9 @@ ArgumentError: argument 2: exceptions.TypeError: Don't know how to convert parameter 2 >>> -As has been mentioned before, all Python types except integers, strings, and -bytes objects have to be wrapped in their corresponding :mod:`ctypes` type, so -that they can be converted to the required C data type:: +As has been mentioned before, all Python types except integers, strings, +bytes objects, and bytearrays have to be wrapped in their corresponding +:mod:`ctypes` type, so that they can be converted to the required C data type:: >>> printf(b"An int %d, a double %f\n", 1234, c_double(3.14)) An int 1234, a double 3.140000 @@ -384,7 +386,7 @@ You can also customize :mod:`ctypes` argument conversion to allow instances of your own classes be used as function arguments. :mod:`ctypes` looks for an :attr:`_as_parameter_` attribute and uses this as the function argument. Of -course, it must be one of integer, string, or bytes:: +course, it must be one of integer, string, bytes, or bytearray:: >>> class Bottles: ... def __init__(self, number): @@ -439,7 +441,7 @@ whatever is needed to make sure this object is acceptable, and then return the object itself, its :attr:`_as_parameter_` attribute, or whatever you want to pass as the C function argument in this case. Again, the result should be an -integer, string, bytes, a :mod:`ctypes` instance, or an object with an +integer, string, bytes, bytarray, a :mod:`ctypes` instance, or an object with an :attr:`_as_parameter_` attribute. @@ -1775,12 +1777,13 @@ ctypes array of :class:`c_char`. *init_or_size* must be an integer which specifies the size of the array, or a - bytes object which will be used to initialize the array items. - - If a bytes object is specified as first argument, the buffer is made one item - larger than its length so that the last element in the array is a NUL - termination character. An integer can be passed as second argument which allows - to specify the size of the array if the length of the bytes should not be used. + bytes object or a bytearray which will be used to initialize the array items. + + If a bytes object or a bytearray is specified as first argument, the buffer + is made one item larger than its length so that the last element in the + array is a NUL termination character. An integer can be passed as second + argument which allows to specify the size of the array if the length of the + bytes should not be used. @@ -2044,8 +2047,8 @@ This attribute contains the actual value of the instance. For integer and pointer types, it is an integer, for character types, it is a single - character bytes object or string, for character pointer types it is a - Python bytes object or string. + character bytes object, bytearray or string, for character pointer types + it is a Python bytes object, bytearray or string. When the ``value`` attribute is retrieved from a ctypes instance, usually a new object is returned each time. :mod:`ctypes` does *not* implement @@ -2087,7 +2090,7 @@ Represents the C :c:type:`char *` datatype when it points to a zero-terminated string. For a general character pointer that may also point to binary data, ``POINTER(c_char)`` must be used. The constructor accepts an integer - address, or a bytes object. + address, a bytes object, or a bytearray. .. class:: c_double diff -r 06cf4044a11a Lib/ctypes/__init__.py --- a/Lib/ctypes/__init__.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/__init__.py Sat Aug 09 10:00:17 2014 +0300 @@ -49,7 +49,7 @@ create_string_buffer(anInteger) -> character array create_string_buffer(aString, anInteger) -> character array """ - if isinstance(init, bytes): + if isinstance(init, (bytes, bytearray)): if size is None: size = len(init)+1 buftype = c_char * size diff -r 06cf4044a11a Lib/ctypes/test/test_buffers.py --- a/Lib/ctypes/test/test_buffers.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/test/test_buffers.py Sat Aug 09 10:00:17 2014 +0300 @@ -10,16 +10,20 @@ self.assertEqual(sizeof(b), 32 * sizeof(c_char)) self.assertIs(type(b[0]), bytes) - b = create_string_buffer(b"abc") - self.assertEqual(len(b), 4) # trailing nul char - self.assertEqual(sizeof(b), 4 * sizeof(c_char)) - self.assertIs(type(b[0]), bytes) - self.assertEqual(b[0], b"a") - self.assertEqual(b[:], b"abc\0") - self.assertEqual(b[::], b"abc\0") - self.assertEqual(b[::-1], b"\0cba") - self.assertEqual(b[::2], b"ac") - self.assertEqual(b[::5], b"a") + for bytes_type in bytes, bytearray: + with self.subTest(bytes_type=bytes_type): + b = create_string_buffer(bytes_type(b"abc")) + self.assertEqual(len(b), 4) # trailing nul char + self.assertEqual(sizeof(b), 4 * sizeof(c_char)) + self.assertIs(type(b[0]), bytes) + self.assertEqual(b[0], b"a") + self.assertEqual(b[:], b"abc\0") + self.assertEqual(b[::], b"abc\0") + self.assertEqual(b[::-1], b"\0cba") + self.assertEqual(b[::2], b"ac") + self.assertEqual(b[::5], b"a") + + self.assertRaises(TypeError, create_string_buffer, "abc") self.assertRaises(TypeError, create_string_buffer, "abc") @@ -47,6 +51,8 @@ self.assertRaises(TypeError, create_unicode_buffer, b"abc") + self.assertRaises(TypeError, create_unicode_buffer, b"abc") + @need_symbol('c_wchar') def test_unicode_conversion(self): b = create_unicode_buffer("abc") diff -r 06cf4044a11a Lib/ctypes/test/test_bytes.py --- a/Lib/ctypes/test/test_bytes.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/test/test_bytes.py Sat Aug 09 10:00:17 2014 +0300 @@ -3,18 +3,41 @@ import sys from ctypes import * -class BytesTest(unittest.TestCase): +class AbstractBytesTest: def test_c_char(self): - x = c_char(b"x") + x = c_char(self.bytes_type(b"x")) self.assertRaises(TypeError, c_char, "x") - x.value = b"y" + x.value = self.bytes_type(b"y") with self.assertRaises(TypeError): x.value = "y" - c_char.from_param(b"x") + c_char.from_param(self.bytes_type(b"x")) self.assertRaises(TypeError, c_char.from_param, "x") - (c_char * 3)(b"a", b"b", b"c") + (c_char * 3)(b"a", self.bytes_type(b"b"), self.bytes_type(b"c")) self.assertRaises(TypeError, c_char * 3, "a", "b", "c") + def test_c_char_p(self): + c_char_p(self.bytes_type(b"foo bar")) + self.assertRaises(TypeError, c_char_p, "foo bar") + + def test_struct(self): + class X(Structure): + _fields_ = [("a", c_char * 3)] + + x = X(self.bytes_type(b"abc")) + self.assertRaises(TypeError, X, "abc") + self.assertEqual(x.a, b"abc") + self.assertEqual(type(x.a), bytes) + + +class BytesTest(AbstractBytesTest, unittest.TestCase): + bytes_type = bytes + + +class ByteArrayTest(AbstractBytesTest, unittest.TestCase): + bytes_type = bytearray + + +class WcharTest(unittest.TestCase): def test_c_wchar(self): x = c_wchar("x") self.assertRaises(TypeError, c_wchar, b"x") @@ -26,23 +49,10 @@ (c_wchar * 3)("a", "b", "c") self.assertRaises(TypeError, c_wchar * 3, b"a", b"b", b"c") - def test_c_char_p(self): - c_char_p(b"foo bar") - self.assertRaises(TypeError, c_char_p, "foo bar") - def test_c_wchar_p(self): c_wchar_p("foo bar") self.assertRaises(TypeError, c_wchar_p, b"foo bar") - def test_struct(self): - class X(Structure): - _fields_ = [("a", c_char * 3)] - - x = X(b"abc") - self.assertRaises(TypeError, X, "abc") - self.assertEqual(x.a, b"abc") - self.assertEqual(type(x.a), bytes) - def test_struct_W(self): class X(Structure): _fields_ = [("a", c_wchar * 3)] diff -r 06cf4044a11a Lib/ctypes/test/test_functions.py --- a/Lib/ctypes/test/test_functions.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/test/test_functions.py Sat Aug 09 10:00:17 2014 +0300 @@ -168,8 +168,9 @@ f = dll._testfunc_p_p f.argtypes = None f.restype = c_char_p - result = f(b"123") - self.assertEqual(result, b"123") + for bytes_type in bytes, bytearray: + result = f(bytes_type(b"123")) + self.assertEqual(result, b"123") result = f(None) self.assertEqual(result, None) diff -r 06cf4044a11a Lib/ctypes/test/test_stringptr.py --- a/Lib/ctypes/test/test_stringptr.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/test/test_stringptr.py Sat Aug 09 10:00:17 2014 +0300 @@ -6,7 +6,7 @@ lib = CDLL(_ctypes_test.__file__) -class StringPtrTestCase(unittest.TestCase): +class AbstractStringPtrTestCase: @support.refcount_test def test__POINTER_c_char(self): @@ -16,7 +16,7 @@ # NULL pointer access self.assertRaises(ValueError, getattr, x.str, "contents") - b = c_buffer(b"Hello, World") + b = c_buffer(self.bytes_type(b"Hello, World")) from sys import getrefcount as grc self.assertEqual(grc(b), 2) x.str = b @@ -37,10 +37,10 @@ # c_char_p and Python string is compatible # c_char_p and c_buffer is NOT compatible self.assertEqual(x.str, None) - x.str = b"Hello, World" + x.str = self.bytes_type(b"Hello, World") self.assertEqual(x.str, b"Hello, World") - b = c_buffer(b"Hello, World") - self.assertRaises(TypeError, setattr, x, b"str", b) + b = c_buffer(self.bytes_type(b"Hello, World")) + self.assertRaises(TypeError, setattr, x, self.bytes_type(b"str"), b) def test_functions(self): @@ -50,28 +50,40 @@ # c_char_p and Python string is compatible # c_char_p and c_buffer are now compatible strchr.argtypes = c_char_p, c_char - self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") - self.assertEqual(strchr(c_buffer(b"abcdef"), b"c"), b"cdef") + self.assertEqual(strchr(self.bytes_type(b"abcdef"), + self.bytes_type(b"c")), b"cdef") + self.assertEqual(strchr(c_buffer(self.bytes_type(b"abcdef")), + self.bytes_type(b"c")), b"cdef") # POINTER(c_char) and Python string is NOT compatible # POINTER(c_char) and c_buffer() is compatible strchr.argtypes = POINTER(c_char), c_char - buf = c_buffer(b"abcdef") - self.assertEqual(strchr(buf, b"c"), b"cdef") - self.assertEqual(strchr(b"abcdef", b"c"), b"cdef") + buf = c_buffer(self.bytes_type(b"abcdef")) + self.assertEqual(strchr(buf, self.bytes_type(b"c")), b"cdef") + self.assertEqual(strchr(self.bytes_type(b"abcdef"), + self.bytes_type(b"c")), b"cdef") # XXX These calls are dangerous, because the first argument # to strchr is no longer valid after the function returns! # So we must keep a reference to buf separately strchr.restype = POINTER(c_char) - buf = c_buffer(b"abcdef") - r = strchr(buf, b"c") + buf = c_buffer(self.bytes_type(b"abcdef")) + r = strchr(buf, self.bytes_type(b"c")) x = r[0], r[1], r[2], r[3], r[4] self.assertEqual(x, (b"c", b"d", b"e", b"f", b"\000")) del buf # x1 will NOT be the same as x, usually: x1 = r[0], r[1], r[2], r[3], r[4] + +class BytesStringPtrTestCase(AbstractStringPtrTestCase, unittest.TestCase): + bytes_type = bytes + + +class BytearrayStringPtrTestCase(AbstractStringPtrTestCase, unittest.TestCase): + bytes_type = bytearray + + if __name__ == '__main__': unittest.main() diff -r 06cf4044a11a Lib/ctypes/test/test_structures.py --- a/Lib/ctypes/test/test_structures.py Sat Aug 09 09:34:25 2014 +0300 +++ b/Lib/ctypes/test/test_structures.py Sat Aug 09 10:00:17 2014 +0300 @@ -322,7 +322,7 @@ self.assertEqual(cls, RuntimeError) self.assertEqual(msg, "(Phone) : " - "expected bytes, int found") + "expected bytes or bytearray, int found") cls, msg = self.get_except(Person, b"Someone", (b"a", b"b", b"c")) self.assertEqual(cls, RuntimeError) diff -r 06cf4044a11a Modules/_ctypes/_ctypes.c --- a/Modules/_ctypes/_ctypes.c Sat Aug 09 09:34:25 2014 +0300 +++ b/Modules/_ctypes/_ctypes.c Sat Aug 09 10:00:17 2014 +0300 @@ -1122,14 +1122,22 @@ return -1; } - if (!PyBytes_Check(value)) { + if (PyBytes_Check(value)) { + Py_INCREF(value); + size = PyBytes_GET_SIZE(value); + ptr = PyBytes_AS_STRING(value); + } + else if (PyByteArray_Check(value)) { + Py_INCREF(value); + size = PyByteArray_GET_SIZE(value); + ptr = PyByteArray_AS_STRING(value); + } + else { PyErr_Format(PyExc_TypeError, - "bytes expected instead of %s instance", + "bytes or bytearray expected instead of %s instance", Py_TYPE(value)->tp_name); return -1; - } else - Py_INCREF(value); - size = PyBytes_GET_SIZE(value); + } if (size > self->b_size) { PyErr_SetString(PyExc_ValueError, "byte string too long"); @@ -1137,7 +1145,6 @@ return -1; } - ptr = PyBytes_AS_STRING(value); memcpy(self->b_ptr, ptr, size); if (size < self->b_size) self->b_ptr[size] = '\0'; @@ -1536,7 +1543,7 @@ Py_INCREF(Py_None); return Py_None; } - if (PyBytes_Check(value)) { + if (PyBytes_Check(value) || PyByteArray_Check(value)) { PyCArgObject *parg; struct fielddesc *fd = _ctypes_get_fielddesc("z"); @@ -1624,7 +1631,7 @@ } /* XXX struni: remove later */ /* bytes */ - if (PyBytes_Check(value)) { + if (PyBytes_Check(value) || PyByteArray_Check(value)) { PyCArgObject *parg; struct fielddesc *fd = _ctypes_get_fielddesc("z"); diff -r 06cf4044a11a Modules/_ctypes/callproc.c --- a/Modules/_ctypes/callproc.c Sat Aug 09 09:34:25 2014 +0300 +++ b/Modules/_ctypes/callproc.c Sat Aug 09 10:00:17 2014 +0300 @@ -667,6 +667,14 @@ return 0; } + if (PyByteArray_Check(obj)) { + pa->ffi_type = &ffi_type_pointer; + pa->value.p = PyByteArray_AsString(obj); + Py_INCREF(obj); + pa->keep = obj; + return 0; + } + #ifdef CTYPES_UNICODE if (PyUnicode_Check(obj)) { pa->ffi_type = &ffi_type_pointer; diff -r 06cf4044a11a Modules/_ctypes/cfield.c --- a/Modules/_ctypes/cfield.c Sat Aug 09 09:34:25 2014 +0300 +++ b/Modules/_ctypes/cfield.c Sat Aug 09 10:00:17 2014 +0300 @@ -1293,14 +1293,17 @@ if(PyBytes_Check(value)) { Py_INCREF(value); + data = PyBytes_AS_STRING(value); + } else if(PyByteArray_Check(value)) { + Py_INCREF(value); + data = PyByteArray_AS_STRING(value); } else { PyErr_Format(PyExc_TypeError, - "expected bytes, %s found", + "expected bytes or bytearray, %s found", value->ob_type->tp_name); return NULL; } - data = PyBytes_AS_STRING(value); if (!data) return NULL; size = strlen(data); /* XXX Why not Py_SIZE(value)? */ @@ -1335,7 +1338,13 @@ *(char **)ptr = PyBytes_AsString(value); Py_INCREF(value); return value; - } else if (PyLong_Check(value)) { + } + if (PyByteArray_Check(value)) { + *(char **)ptr = PyByteArray_AsString(value); + Py_INCREF(value); + return value; + } + if (PyLong_Check(value)) { #if SIZEOF_VOID_P == SIZEOF_LONG_LONG *(char **)ptr = (char *)PyLong_AsUnsignedLongLongMask(value); #else