X Tutup
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; namespace Python.Runtime { public sealed class PyBuffer : IDisposable { private PyObject _exporter; private Py_buffer _view; internal PyBuffer(PyObject exporter, PyBUF flags) { _view = new Py_buffer(); if (Runtime.PyObject_GetBuffer(exporter, out _view, (int)flags) < 0) { throw PythonException.ThrowLastAsClrException(); } _exporter = exporter; var intPtrBuf = new IntPtr[_view.ndim]; if (_view.shape != IntPtr.Zero) { Marshal.Copy(_view.shape, intPtrBuf, 0, _view.ndim); Shape = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.strides != IntPtr.Zero) { Marshal.Copy(_view.strides, intPtrBuf, 0, _view.ndim); Strides = intPtrBuf.Select(x => (long)x).ToArray(); } if (_view.suboffsets != IntPtr.Zero) { Marshal.Copy(_view.suboffsets, intPtrBuf, 0, _view.ndim); SubOffsets = intPtrBuf.Select(x => (long)x).ToArray(); } } public PyObject Object => _exporter; public long Length => (long)_view.len; public long ItemSize => (long)_view.itemsize; public int Dimensions => _view.ndim; public bool ReadOnly => _view._readonly; public IntPtr Buffer => _view.buf; public string? Format => _view.format; /// /// An array of length indicating the shape of the memory as an n-dimensional array. /// public long[]? Shape { get; private set; } /// /// An array of length giving the number of bytes to skip to get to a new element in each dimension. /// Will be null except when PyBUF_STRIDES or PyBUF_INDIRECT flags in GetBuffer/>. /// public long[]? Strides { get; private set; } /// /// An array of Py_ssize_t of length ndim. If suboffsets[n] >= 0, /// the values stored along the nth dimension are pointers and the suboffset value dictates how many bytes to add to each pointer after de-referencing. /// A suboffset value that is negative indicates that no de-referencing should occur (striding in a contiguous memory block). /// public long[]? SubOffsets { get; private set; } private static char OrderStyleToChar(BufferOrderStyle order, bool eitherOneValid) { char style = 'C'; if (order == BufferOrderStyle.C) style = 'C'; else if (order == BufferOrderStyle.Fortran) style = 'F'; else if (order == BufferOrderStyle.EitherOne) { if (eitherOneValid) style = 'A'; else throw new ArgumentException("BufferOrderStyle can not be EitherOne and has to be C or Fortran"); } return style; } /// /// Return the implied itemsize from format. On error, raise an exception and return -1. /// New in version 3.9. /// public static long SizeFromFormat(string format) { if (Runtime.PyVersion < new Version(3,9)) throw new NotSupportedException("SizeFromFormat requires at least Python 3.9"); nint result = Runtime.PyBuffer_SizeFromFormat(format); if (result == -1) throw PythonException.ThrowLastAsClrException(); return result; } /// /// Returns true if the memory defined by the view is C-style (order is 'C') or Fortran-style (order is 'F') contiguous or either one (order is 'A'). Returns false otherwise. /// /// C-style (order is 'C') or Fortran-style (order is 'F') contiguous or either one (order is 'A') public bool IsContiguous(BufferOrderStyle order) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); return Convert.ToBoolean(Runtime.PyBuffer_IsContiguous(ref _view, OrderStyleToChar(order, true))); } /// /// Get the memory area pointed to by the indices inside the given view. indices must point to an array of view->ndim indices. /// public IntPtr GetPointer(long[] indices) { if (indices is null) throw new ArgumentNullException(nameof(indices)); if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyVersion < new Version(3, 7)) throw new NotSupportedException("GetPointer requires at least Python 3.7"); return Runtime.PyBuffer_GetPointer(ref _view, indices.Select(x => checked((nint)x)).ToArray()); } /// /// Copy contiguous len bytes from buf to view. fort can be 'C' or 'F' (for C-style or Fortran-style ordering). /// public void FromContiguous(IntPtr buf, long len, BufferOrderStyle fort) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyVersion < new Version(3, 7)) throw new NotSupportedException("FromContiguous requires at least Python 3.7"); if (Runtime.PyBuffer_FromContiguous(ref _view, buf, checked((nint)len), OrderStyleToChar(fort, false)) < 0) throw PythonException.ThrowLastAsClrException(); } /// /// Copy len bytes from view to its contiguous representation in buf. order can be 'C' or 'F' or 'A' (for C-style or Fortran-style ordering or either one). 0 is returned on success, -1 on error. /// /// order can be 'C' or 'F' or 'A' (for C-style or Fortran-style ordering or either one). /// Buffer to copy to public void ToContiguous(IntPtr buf, BufferOrderStyle order) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyBuffer_ToContiguous(buf, ref _view, _view.len, OrderStyleToChar(order, true)) < 0) throw PythonException.ThrowLastAsClrException(); } /// /// Fill the strides array with byte-strides of a contiguous (C-style if order is 'C' or Fortran-style if order is 'F') array of the given shape with the given number of bytes per element. /// internal static void FillContiguousStrides(int ndims, IntPtr shape, IntPtr strides, int itemsize, BufferOrderStyle order) { Runtime.PyBuffer_FillContiguousStrides(ndims, shape, strides, itemsize, OrderStyleToChar(order, false)); } /// /// FillInfo Method /// /// /// Handle buffer requests for an exporter that wants to expose buf of size len with writability set according to readonly. buf is interpreted as a sequence of unsigned bytes. /// The flags argument indicates the request type. This function always fills in view as specified by flags, unless buf has been designated as read-only and PyBUF_WRITABLE is set in flags. /// On success, set view->obj to a new reference to exporter and return 0. Otherwise, raise PyExc_BufferError, set view->obj to NULL and return -1; /// If this function is used as part of a getbufferproc, exporter MUST be set to the exporting object and flags must be passed unmodified.Otherwise, exporter MUST be NULL. /// /// On success, set view->obj to a new reference to exporter and return 0. Otherwise, raise PyExc_BufferError, set view->obj to NULL and return -1; internal void FillInfo(BorrowedReference exporter, IntPtr buf, long len, bool _readonly, int flags) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (Runtime.PyBuffer_FillInfo(ref _view, exporter, buf, (IntPtr)len, Convert.ToInt32(_readonly), flags) < 0) throw PythonException.ThrowLastAsClrException(); } /// /// Writes a managed byte array into the buffer of a python object. This can be used to pass data like images from managed to python. /// public void Write(byte[] buffer, int sourceOffset, int count, nint destinationOffset) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (_view.ndim != 1) throw new NotImplementedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); if (!this.IsContiguous(BufferOrderStyle.C)) throw new NotImplementedException("Only continuous buffers are supported"); if (ReadOnly) throw new InvalidOperationException("Buffer is read-only"); if (buffer is null) throw new ArgumentNullException(nameof(buffer)); if (sourceOffset < 0) throw new IndexOutOfRangeException($"{nameof(sourceOffset)} is negative"); if (destinationOffset < 0) throw new IndexOutOfRangeException($"{nameof(destinationOffset)} is negative"); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Value must be >= 0"); if (checked(count + sourceOffset) > buffer.Length) throw new ArgumentOutOfRangeException("count", "Count is bigger than the buffer."); if (checked(count + destinationOffset) > _view.len) throw new ArgumentOutOfRangeException("count", "Count is bigger than the python buffer."); Marshal.Copy(buffer, sourceOffset, _view.buf + destinationOffset, count); } /// /// Reads the buffer of a python object into a managed byte array. This can be used to pass data like images from python to managed. /// public void Read(byte[] buffer, int destinationOffset, int count, nint sourceOffset) { if (disposedValue) throw new ObjectDisposedException(nameof(PyBuffer)); if (_view.ndim != 1) throw new NotImplementedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); if (!this.IsContiguous(BufferOrderStyle.C)) throw new NotImplementedException("Only continuous buffers are supported"); if (buffer is null) throw new ArgumentNullException(nameof(buffer)); if (sourceOffset < 0) throw new IndexOutOfRangeException($"{nameof(sourceOffset)} is negative"); if (destinationOffset < 0) throw new IndexOutOfRangeException($"{nameof(destinationOffset)} is negative"); if (count < 0) throw new ArgumentOutOfRangeException(nameof(count), count, "Value must be >= 0"); if (checked(count + destinationOffset) > buffer.Length) throw new ArgumentOutOfRangeException("count", "Count is bigger than the buffer."); if (checked(count + sourceOffset) > _view.len) throw new ArgumentOutOfRangeException("count", "Count is bigger than the python buffer."); Marshal.Copy(_view.buf + sourceOffset, buffer, destinationOffset, count); } private bool disposedValue = false; // To detect redundant calls private void Dispose(bool disposing) { if (!disposedValue) { if (Runtime.Py_IsInitialized() == 0) throw new InvalidOperationException("Python runtime must be initialized"); // this also decrements ref count for _view->obj Runtime.PyBuffer_Release(ref _view); _exporter = null!; Shape = null; Strides = null; SubOffsets = null; disposedValue = true; } } ~PyBuffer() { Debug.Assert(!disposedValue); if (_view.obj != IntPtr.Zero) { Finalizer.Instance.AddFinalizedBuffer(ref _view); } Dispose(false); } /// /// Release the buffer view and decrement the reference count for view->obj. This function MUST be called when the buffer is no longer being used, otherwise reference leaks may occur. /// It is an error to call this function on a buffer that was not obtained via . /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }
X Tutup