From 350653141a8de232d73f1db016fba52b9080872d Mon Sep 17 00:00:00 2001 From: Rian Hunter Date: Sun, 12 Nov 2023 21:24:00 -0500 Subject: [PATCH] Implement `ctypes.buffer_at()` ctypes currently has no way to easily create a buffer object from a pointer and a dynamic size. It is possible to create memoryview objects of array objects (e.g. memoryview((c_ubyte * 10)())) but this is excessively slow when implementing a callback function in Python that is passed a dynamic void * and a size_t. `ctypes.buffer_at()` fills that gap in the API. This is similar to `ffi.buffer()` in the cffi module. --- Doc/library/ctypes.rst | 13 +++++++++++++ Lib/ctypes/__init__.py | 8 ++++++++ Modules/_ctypes/_ctypes.c | 12 ++++++++++++ 3 files changed, 33 insertions(+) diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst index ef3a9a0f5898af..c28a378d181b59 100644 --- a/Doc/library/ctypes.rst +++ b/Doc/library/ctypes.rst @@ -2109,6 +2109,19 @@ Utility functions .. audit-event:: ctypes.wstring_at address,size ctypes.wstring_at +.. function:: buffer_at(address, size, allow_write=False) + + This function returns a memoryview object that references the + memory starting at *address* up to (but not including) *address + + size*. If *allow_write* is set to a truthy value then the + memoryview object is mutable. + + This function is similar to :funct:`string_at` with the key difference + of not making a copy of the specified memory. + + .. audit-event:: ctypes.buffer_at address,size,allow_write ctypes.buffer_at + + .. _ctypes-data-types: Data types diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index 141142a57dcb3e..674286550730d7 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -538,6 +538,14 @@ def wstring_at(ptr, size=-1): Return the string at addr.""" return _wstring_at(ptr, size) +from _ctypes import _buffer_at_addr + +_buffer_at = PYFUNCTYPE(py_object, c_void_p, c_int, c_int)(_buffer_at_addr) +def buffer_at(ptr, size, allow_write=False): + """buffer_at(addr, size[, allow_write]) -> memoryview + + Return the buffer at addr.""" + return _buffer_at(ptr, size, bool(allow_write)) if _os.name == "nt": # COM stuff def DllGetClassObject(rclsid, riid, ppv): diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 6e0709cc1e4a4d..a77824b592d4a9 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -5642,6 +5642,17 @@ wstring_at(const wchar_t *ptr, int size) return PyUnicode_FromWideChar(ptr, ssize); } +static PyObject * +buffer_at(char *ptr, int size, int allow_write) +{ + if (PySys_Audit("ctypes.buffer_at", "nii", (Py_ssize_t)ptr, size, + allow_write) < 0) { + return NULL; + } + + return PyMemoryView_FromMemory(ptr, size, + allow_write ? PyBUF_WRITE : PyBUF_READ); +} static struct PyModuleDef _ctypesmodule = { PyModuleDef_HEAD_INIT, @@ -5777,6 +5788,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("_buffer_at_addr", PyLong_FromVoidPtr(buffer_at)); /* If RTLD_LOCAL is not defined (Windows!), set it to zero. */ #if !HAVE_DECL_RTLD_LOCAL