.. audit-event:: ctypes.wstring_at ptr,size ctypes.wstring_at
+.. function:: memoryview_at(ptr, size, readonly=False)
+
+ Return a :class:`memoryview` object of length *size* that references memory
+ starting at *void \*ptr*.
+
+ If *readonly* is true, the returned :class:`!memoryview` object can
+ not be used to modify the underlying memory.
+ (Changes made by other means will still be reflected in the returned
+ object.)
+
+ This function is similar to :func:`string_at` with the key
+ difference of not making a copy of the specified memory.
+ It is a semantically equivalent (but more efficient) alternative to
+ ``memoryview((c_byte * size).from_address(ptr))``.
+ (While :meth:`~_CData.from_address` only takes integers, *ptr* can also
+ be given as a :class:`ctypes.POINTER` or a :func:`~ctypes.byref` object.)
+
+ .. audit-event:: ctypes.memoryview_at address,size,readonly
+
+ .. versionadded:: next
+
+
.. _ctypes-data-types:
Data types
* On Windows, the :func:`~ctypes.CopyComPointer` function is now public.
(Contributed by Jun Komoda in :gh:`127275`.)
+* :func:`ctypes.memoryview_at` now exists to create a
+ :class:`memoryview` object that refers to the supplied pointer and
+ length. This works like :func:`ctypes.string_at` except it avoids a
+ buffer copy, and is typically useful when implementing pure Python
+ callback functions that are passed dynamically-sized buffers.
+ (Contributed by Rian Hunter in :gh:`112018`.)
+
+
datetime
--------
# functions
from _ctypes import _memmove_addr, _memset_addr, _string_at_addr, _cast_addr
+from _ctypes import _memoryview_at_addr
## void *memmove(void *, const void *, size_t);
memmove = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_size_t)(_memmove_addr)
Return the byte string at void *ptr."""
return _string_at(ptr, size)
+_memoryview_at = PYFUNCTYPE(
+ py_object, c_void_p, c_ssize_t, c_int)(_memoryview_at_addr)
+def memoryview_at(ptr, size, readonly=False):
+ """memoryview_at(ptr, size[, readonly]) -> memoryview
+
+ Return a memoryview representing the memory at void *ptr."""
+ return _memoryview_at(ptr, size, bool(readonly))
+
try:
from _ctypes import _wstring_at_addr
except ImportError:
create_string_buffer, string_at,
create_unicode_buffer, wstring_at,
memmove, memset,
- c_char_p, c_byte, c_ubyte, c_wchar)
+ memoryview_at, c_void_p,
+ c_char_p, c_byte, c_ubyte, c_wchar,
+ addressof, byref)
class MemFunctionsTest(unittest.TestCase):
self.assertEqual(wstring_at(a, 16), "Hello, World\0\0\0\0")
self.assertEqual(wstring_at(a, 0), "")
+ def test_memoryview_at(self):
+ b = (c_byte * 10)()
+
+ size = len(b)
+ for foreign_ptr in (
+ b,
+ cast(b, c_void_p),
+ byref(b),
+ addressof(b),
+ ):
+ with self.subTest(foreign_ptr=type(foreign_ptr).__name__):
+ b[:] = b"initialval"
+ v = memoryview_at(foreign_ptr, size)
+ self.assertIsInstance(v, memoryview)
+ self.assertEqual(bytes(v), b"initialval")
+
+ # test that writes to source buffer get reflected in memoryview
+ b[:] = b"0123456789"
+ self.assertEqual(bytes(v), b"0123456789")
+
+ # test that writes to memoryview get reflected in source buffer
+ v[:] = b"9876543210"
+ self.assertEqual(bytes(b), b"9876543210")
+
+ with self.assertRaises(ValueError):
+ memoryview_at(foreign_ptr, -1)
+
+ with self.assertRaises(ValueError):
+ memoryview_at(foreign_ptr, sys.maxsize + 1)
+
+ v0 = memoryview_at(foreign_ptr, 0)
+ self.assertEqual(bytes(v0), b'')
+
+ def test_memoryview_at_readonly(self):
+ b = (c_byte * 10)()
+
+ size = len(b)
+ for foreign_ptr in (
+ b,
+ cast(b, c_void_p),
+ byref(b),
+ addressof(b),
+ ):
+ with self.subTest(foreign_ptr=type(foreign_ptr).__name__):
+ b[:] = b"initialval"
+ v = memoryview_at(foreign_ptr, size, readonly=True)
+ self.assertIsInstance(v, memoryview)
+ self.assertEqual(bytes(v), b"initialval")
+
+ # test that writes to source buffer get reflected in memoryview
+ b[:] = b"0123456789"
+ self.assertEqual(bytes(v), b"0123456789")
+
+ # test that writes to the memoryview are blocked
+ with self.assertRaises(TypeError):
+ v[:] = b"9876543210"
if __name__ == "__main__":
unittest.main()
--- /dev/null
+:func:`ctypes.memoryview_at` now exists to create a
+:class:`memoryview` object that refers to the supplied pointer and
+length. This works like :func:`ctypes.string_at` except it avoids a
+buffer copy, and is typically useful when implementing pure Python
+callback functions that are passed dynamically-sized buffers.
return PyUnicode_FromWideChar(ptr, ssize);
}
+static PyObject *
+memoryview_at(void *ptr, Py_ssize_t size, int readonly)
+{
+ if (PySys_Audit("ctypes.memoryview_at", "nni",
+ (Py_ssize_t)ptr, size, readonly) < 0) {
+ return NULL;
+ }
+ if (size < 0) {
+ PyErr_Format(PyExc_ValueError,
+ "memoryview_at: size is negative (or overflowed): %zd",
+ size);
+ return NULL;
+ }
+ return PyMemoryView_FromMemory(ptr, size,
+ readonly ? PyBUF_READ : PyBUF_WRITE);
+}
static int
_ctypes_add_types(PyObject *mod)
MOD_ADD("_string_at_addr", PyLong_FromVoidPtr(string_at));
MOD_ADD("_cast_addr", PyLong_FromVoidPtr(cast));
MOD_ADD("_wstring_at_addr", PyLong_FromVoidPtr(wstring_at));
+ MOD_ADD("_memoryview_at_addr", PyLong_FromVoidPtr(memoryview_at));
/* If RTLD_LOCAL is not defined (Windows!), set it to zero. */
#if !HAVE_DECL_RTLD_LOCAL