mirror of
https://github.com/python/cpython.git
synced 2025-12-31 04:23:37 +00:00
gh-112015: Implement ctypes.memoryview_at() (GH-112018)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
This commit is contained in:
parent
f21af186bf
commit
b4f799b1e7
6 changed files with 120 additions and 1 deletions
|
|
@ -2182,6 +2182,28 @@ Utility functions
|
|||
.. 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
|
||||
|
|
|
|||
|
|
@ -343,6 +343,14 @@ ctypes
|
|||
* 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
|
||||
--------
|
||||
|
||||
|
|
|
|||
|
|
@ -524,6 +524,7 @@ def WinError(code=None, descr=None):
|
|||
# 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)
|
||||
|
|
@ -549,6 +550,14 @@ def string_at(ptr, size=-1):
|
|||
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:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
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):
|
||||
|
|
@ -77,6 +79,62 @@ def test_wstring_at(self):
|
|||
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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
: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.
|
||||
|
|
@ -5791,6 +5791,22 @@ wstring_at(const wchar_t *ptr, int size)
|
|||
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)
|
||||
|
|
@ -5919,6 +5935,7 @@ _ctypes_add_objects(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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue