From 3c9f7b3edf801bd6ff0024196cb6a8126dd84f47 Mon Sep 17 00:00:00 2001 From: SnGmng <38666407+SnGmng@users.noreply.github.com> Date: Sun, 27 Oct 2019 20:35:42 +0100 Subject: [PATCH 1/3] Add Python buffer api support --- AUTHORS.md | 1 + CHANGELOG.md | 1 + src/embed_tests/Python.EmbeddingTest.csproj | 1 + src/embed_tests/TestPyBuffer.cs | 72 ++++++ src/runtime/Python.Runtime.csproj | 4 +- src/runtime/bufferinterface.cs | 106 +++++++++ src/runtime/pybuffer.cs | 244 ++++++++++++++++++++ src/runtime/pyobject.cs | 14 ++ src/runtime/runtime.cs | 33 +++ 9 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 src/embed_tests/TestPyBuffer.cs create mode 100644 src/runtime/bufferinterface.cs create mode 100644 src/runtime/pybuffer.cs diff --git a/AUTHORS.md b/AUTHORS.md index 19cd4f5ed..501c28845 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -69,6 +69,7 @@ - ([@OneBlue](https://github.com/OneBlue)) - ([@rico-chet](https://github.com/rico-chet)) - ([@rmadsen-ks](https://github.com/rmadsen-ks)) +- ([@SnGmng](https://github.com/SnGmng)) - ([@stonebig](https://github.com/stonebig)) - ([@testrunner123](https://github.com/testrunner123)) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a815d85..28a8790df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Added Runtime.None to be able to pass None as parameter into Python from .NET - Added PyObject.IsNone() to check if a Python object is None in .NET. - Support for Python 3.8 +- Added Python 3 buffer api support and PyBuffer interface for fast byte and numpy array read/write ([#980][p980]) ### Changed diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj index 5dee66e64..eff226dd5 100644 --- a/src/embed_tests/Python.EmbeddingTest.csproj +++ b/src/embed_tests/Python.EmbeddingTest.csproj @@ -97,6 +97,7 @@ + diff --git a/src/embed_tests/TestPyBuffer.cs b/src/embed_tests/TestPyBuffer.cs new file mode 100644 index 000000000..3919c4dfb --- /dev/null +++ b/src/embed_tests/TestPyBuffer.cs @@ -0,0 +1,72 @@ +using System.Text; +using NUnit.Framework; +using Python.Runtime; + +namespace Python.EmbeddingTest { + class TestPyBuffer + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void TestBufferWrite() + { + if (Runtime.Runtime.pyversionnumber < 35) return; + + string bufferTestString = "hello world! !$%&/()=?"; + + using (Py.GIL()) + { + using (var scope = Py.CreateScope()) + { + scope.Exec($"arr = bytearray({bufferTestString.Length})"); + PyObject pythonArray = scope.Get("arr"); + byte[] managedArray = new UTF8Encoding().GetBytes(bufferTestString); + + using (PyBuffer buf = pythonArray.GetBuffer()) + { + buf.Write(managedArray, 0, managedArray.Length); + } + + string result = scope.Eval("arr.decode('utf-8')").ToString(); + Assert.IsTrue(result == bufferTestString); + } + } + } + + [Test] + public void TestBufferRead() + { + if (Runtime.Runtime.pyversionnumber < 35) return; + + string bufferTestString = "hello world! !$%&/()=?"; + + using (Py.GIL()) + { + using (var scope = Py.CreateScope()) + { + scope.Exec($"arr = b'{bufferTestString}'"); + PyObject pythonArray = scope.Get("arr"); + byte[] managedArray = new byte[bufferTestString.Length]; + + using (PyBuffer buf = pythonArray.GetBuffer()) + { + buf.Read(managedArray, 0, managedArray.Length); + } + + string result = new UTF8Encoding().GetString(managedArray); + Assert.IsTrue(result == bufferTestString); + } + } + } + } +} diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 75f5e2fab..070d87835 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -89,6 +89,7 @@ + @@ -130,6 +131,7 @@ + @@ -183,4 +185,4 @@ - \ No newline at end of file + diff --git a/src/runtime/bufferinterface.cs b/src/runtime/bufferinterface.cs new file mode 100644 index 000000000..0c0ac2140 --- /dev/null +++ b/src/runtime/bufferinterface.cs @@ -0,0 +1,106 @@ +using System; +using System.Runtime.InteropServices; + +namespace Python.Runtime +{ + /* buffer interface */ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] + internal struct Py_buffer { + public IntPtr buf; + public IntPtr obj; /* owned reference */ + [MarshalAs(UnmanagedType.SysInt)] + public IntPtr len; + [MarshalAs(UnmanagedType.SysInt)] + public IntPtr itemsize; /* This is Py_ssize_t so it can be + pointed to by strides in simple case.*/ + [MarshalAs(UnmanagedType.Bool)] + public bool _readonly; + public int ndim; + [MarshalAs(UnmanagedType.LPStr)] + public string format; + public IntPtr shape; + public IntPtr strides; + public IntPtr suboffsets; + public IntPtr _internal; + } + + public enum BufferOrderStyle + { + C, + Fortran, + EitherOne, + } + + /* Flags for getting buffers */ + public enum PyBUF + { + /// + /// Simple buffer without shape strides and suboffsets + /// + SIMPLE = 0, + /// + /// Controls the field. If set, the exporter MUST provide a writable buffer or else report failure. Otherwise, the exporter MAY provide either a read-only or writable buffer, but the choice MUST be consistent for all consumers. + /// + WRITABLE = 0x0001, + /// + /// Controls the field. If set, this field MUST be filled in correctly. Otherwise, this field MUST be NULL. + /// + FORMATS = 0x0004, + /// + /// N-Dimensional buffer with shape + /// + ND = 0x0008, + /// + /// Buffer with strides and shape + /// + STRIDES = (0x0010 | ND), + /// + /// C-Contigous buffer with strides and shape + /// + C_CONTIGUOUS = (0x0020 | STRIDES), + /// + /// F-Contigous buffer with strides and shape + /// + F_CONTIGUOUS = (0x0040 | STRIDES), + /// + /// C or Fortran contigous buffer with strides and shape + /// + ANY_CONTIGUOUS = (0x0080 | STRIDES), + /// + /// Buffer with suboffsets (if needed) + /// + INDIRECT = (0x0100 | STRIDES), + /// + /// Writable C-Contigous buffer with shape + /// + CONTIG = (ND | WRITABLE), + /// + /// Readonly C-Contigous buffer with shape + /// + CONTIG_RO = (ND), + /// + /// Writable buffer with shape and strides + /// + STRIDED = (STRIDES | WRITABLE), + /// + /// Readonly buffer with shape and strides + /// + STRIDED_RO = (STRIDES), + /// + /// Writable buffer with shape, strides and format + /// + RECORDS = (STRIDES | WRITABLE | FORMATS), + /// + /// Readonly buffer with shape, strides and format + /// + RECORDS_RO = (STRIDES | FORMATS), + /// + /// Writable indirect buffer with shape, strides, format and suboffsets (if needed) + /// + FULL = (INDIRECT | WRITABLE | FORMATS), + /// + /// Readonly indirect buffer with shape, strides, format and suboffsets (if needed) + /// + FULL_RO = (INDIRECT | FORMATS), + } +} diff --git a/src/runtime/pybuffer.cs b/src/runtime/pybuffer.cs new file mode 100644 index 000000000..f1d853c75 --- /dev/null +++ b/src/runtime/pybuffer.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +namespace Python.Runtime +{ + public sealed class PyBuffer : IDisposable + { + private PyObject _exporter; + private Py_buffer _view; + + unsafe internal PyBuffer(PyObject exporter, PyBUF flags) + { + _view = new Py_buffer(); + + if (Runtime.PyObject_GetBuffer(exporter.Handle, ref _view, (int)flags) < 0) + { + throw new PythonException(); + } + + _exporter = exporter; + + var intPtrBuf = new IntPtr[_view.ndim]; + if (_view.shape != IntPtr.Zero) + { + Marshal.Copy(_view.shape, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Shape = intPtrBuf.Select(x => (long)x).ToArray(); + } + + if (_view.strides != IntPtr.Zero) { + Marshal.Copy(_view.strides, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + Strides = intPtrBuf.Select(x => (long)x).ToArray(); + } + + if (_view.suboffsets != IntPtr.Zero) { + Marshal.Copy(_view.suboffsets, intPtrBuf, 0, (int)_view.len * sizeof(IntPtr)); + 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.pyversionnumber < 39) + throw new NotSupportedException("SizeFromFormat requires at least Python 3.9"); + return (long)Runtime.PyBuffer_SizeFromFormat(format); + } + + /// + /// 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 (disposedValue) + throw new ObjectDisposedException(nameof(PyBuffer)); + if (Runtime.pyversionnumber < 37) + throw new NotSupportedException("GetPointer requires at least Python 3.7"); + return Runtime.PyBuffer_GetPointer(ref _view, indices.Select(x => (IntPtr)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.pyversionnumber < 37) + throw new NotSupportedException("FromContiguous requires at least Python 3.7"); + + if (Runtime.PyBuffer_FromContiguous(ref _view, buf, (IntPtr)len, OrderStyleToChar(fort, false)) < 0) + throw new PythonException(); + } + + /// + /// 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.pyversionnumber < 36) + throw new NotSupportedException("ToContiguous requires at least Python 3.6"); + + if (Runtime.PyBuffer_ToContiguous(buf, ref _view, _view.len, OrderStyleToChar(order, true)) < 0) + throw new PythonException(); + } + + /// + /// 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. + /// + public 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; + public void FillInfo(IntPtr 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 new PythonException(); + } + + /// + /// 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 offset, int count) + { + if (disposedValue) + throw new ObjectDisposedException(nameof(PyBuffer)); + if (ReadOnly) + throw new InvalidOperationException("Buffer is read-only"); + if ((long)_view.len > int.MaxValue) + throw new NotSupportedException("Python buffers bigger than int.MaxValue are not supported."); + if (count > buffer.Length) + throw new ArgumentOutOfRangeException("count", "Count is bigger than the buffer."); + if (count > (int)_view.len) + throw new ArgumentOutOfRangeException("count", "Count is bigger than the python buffer."); + if (_view.ndim != 1) + throw new NotSupportedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); + + Marshal.Copy(buffer, offset, _view.buf, 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 int Read(byte[] buffer, int offset, int count) { + if (disposedValue) + throw new ObjectDisposedException(nameof(PyBuffer)); + if (count > buffer.Length) + throw new ArgumentOutOfRangeException("count", "Count is bigger than the buffer."); + if (_view.ndim != 1) + throw new NotSupportedException("Multidimensional arrays, scalars and objects without a buffer are not supported."); + if (_view.len.ToInt64() > int.MaxValue) + throw new NotSupportedException("Python buffers bigger than int.MaxValue are not supported."); + + int copylen = count < (int)_view.len ? count : (int)_view.len; + Marshal.Copy(_view.buf, buffer, offset, copylen); + return copylen; + } + + private bool disposedValue = false; // To detect redundant calls + + private void Dispose(bool disposing) + { + if (!disposedValue) { + Runtime.PyBuffer_Release(ref _view); + + _exporter = null; + Shape = null; + Strides = null; + SubOffsets = null; + + disposedValue = true; + } + } + + ~PyBuffer() + { + 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); + } + } +} diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs index 699ebf873..3abaf8d72 100644 --- a/src/runtime/pyobject.cs +++ b/src/runtime/pyobject.cs @@ -1092,6 +1092,20 @@ public override int GetHashCode() return ((ulong)Runtime.PyObject_Hash(obj)).GetHashCode(); } + /// + /// GetBuffer Method. This Method only works for objects that have a buffer (like "bytes", "bytearray" or "array.array") + /// + /// + /// Send a request to the PyObject to fill in view as specified by flags. If the PyObject cannot provide a buffer of the exact type, it MUST raise PyExc_BufferError, set view->obj to NULL and return -1. + /// On success, fill in view, set view->obj to a new reference to exporter and return 0. In the case of chained buffer providers that redirect requests to a single object, view->obj MAY refer to this object instead of exporter(See Buffer Object Structures). + /// Successful calls to must be paired with calls to , similar to malloc() and free(). Thus, after the consumer is done with the buffer, must be called exactly once. + /// + public PyBuffer GetBuffer(PyBUF flags = PyBUF.SIMPLE) + { + if (Runtime.pyversionnumber < 35) throw new NotSupportedException("GetBuffer requires at least Python 3.5"); + return new PyBuffer(this, flags); + } + public long Refcount { diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 17511dfe9..81b241d7c 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1072,6 +1072,39 @@ internal static long PyObject_Size(IntPtr pointer) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyObject_Dir(IntPtr pointer); + //==================================================================== + // Python buffer API + //==================================================================== + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyObject_CheckBuffer(IntPtr obj); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyObject_GetBuffer(IntPtr exporter, ref Py_buffer view, int flags); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void PyBuffer_Release(ref Py_buffer view); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern IntPtr PyBuffer_SizeFromFormat([MarshalAs(UnmanagedType.LPStr)] string format); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyBuffer_IsContiguous(ref Py_buffer view, char order); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern IntPtr PyBuffer_GetPointer(ref Py_buffer view, IntPtr[] indices); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyBuffer_FromContiguous(ref Py_buffer view, IntPtr buf, IntPtr len, char fort); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyBuffer_ToContiguous(IntPtr buf, ref Py_buffer src, IntPtr len, char order); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern void PyBuffer_FillContiguousStrides(int ndims, IntPtr shape, IntPtr strides, int itemsize, char order); + + [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] + internal static extern int PyBuffer_FillInfo(ref Py_buffer view, IntPtr exporter, IntPtr buf, IntPtr len, int _readonly, int flags); //==================================================================== // Python number API From 52cc24ccdbf11decd176875ccfd54cf1ef9f0bab Mon Sep 17 00:00:00 2001 From: SnGmng <38666407+SnGmng@users.noreply.github.com> Date: Sat, 23 May 2020 14:46:27 +0200 Subject: [PATCH 2/3] Update finalizer --- src/runtime/pybuffer.cs | 42 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/runtime/pybuffer.cs b/src/runtime/pybuffer.cs index f1d853c75..80c7aa6c1 100644 --- a/src/runtime/pybuffer.cs +++ b/src/runtime/pybuffer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -214,8 +215,45 @@ public int Read(byte[] buffer, int offset, int count) { private void Dispose(bool disposing) { - if (!disposedValue) { - Runtime.PyBuffer_Release(ref _view); + if (!disposedValue) + { + Debug.Assert(_view.obj != IntPtr.Zero, "Buffer object is invalid (no exporter object ref)"); + //if (_view.obj == IntPtr.Zero) + //{ + // return; + //} + + if (Runtime.Py_IsInitialized() == 0) + throw new InvalidOperationException("Python runtime must be initialized"); + + if (!Runtime.IsFinalizing) + { + long refcount = Runtime.Refcount(_view.obj); + Debug.Assert(refcount > 0, "Object refcount is 0 or less"); + + if (refcount == 1) + { + Runtime.PyErr_Fetch(out var errType, out var errVal, out var traceback); + + try + { + // this also decrements ref count for _view->obj + Runtime.PyBuffer_Release(ref _view); + Runtime.CheckExceptionOccurred(); + } + finally + { + // Python requires finalizers to preserve exception: + // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation + Runtime.PyErr_Restore(errType, errVal, traceback); + } + } + else + { + // this also decrements ref count for _view->obj + Runtime.PyBuffer_Release(ref _view); + } + } _exporter = null; Shape = null; From 530e8c719e26a1b08280a877e7e49ae7bac27182 Mon Sep 17 00:00:00 2001 From: SnGmng <38666407+SnGmng@users.noreply.github.com> Date: Fri, 29 May 2020 08:25:18 +0200 Subject: [PATCH 3/3] Update finalizer 2 --- src/runtime/pybuffer.cs | 49 +++++++++++------------------------------ 1 file changed, 13 insertions(+), 36 deletions(-) diff --git a/src/runtime/pybuffer.cs b/src/runtime/pybuffer.cs index 80c7aa6c1..7539e1ac6 100644 --- a/src/runtime/pybuffer.cs +++ b/src/runtime/pybuffer.cs @@ -7,7 +7,7 @@ namespace Python.Runtime { - public sealed class PyBuffer : IDisposable + public sealed class PyBuffer : IPyDisposable { private PyObject _exporter; private Py_buffer _view; @@ -217,43 +217,11 @@ private void Dispose(bool disposing) { if (!disposedValue) { - Debug.Assert(_view.obj != IntPtr.Zero, "Buffer object is invalid (no exporter object ref)"); - //if (_view.obj == IntPtr.Zero) - //{ - // return; - //} - if (Runtime.Py_IsInitialized() == 0) throw new InvalidOperationException("Python runtime must be initialized"); - if (!Runtime.IsFinalizing) - { - long refcount = Runtime.Refcount(_view.obj); - Debug.Assert(refcount > 0, "Object refcount is 0 or less"); - - if (refcount == 1) - { - Runtime.PyErr_Fetch(out var errType, out var errVal, out var traceback); - - try - { - // this also decrements ref count for _view->obj - Runtime.PyBuffer_Release(ref _view); - Runtime.CheckExceptionOccurred(); - } - finally - { - // Python requires finalizers to preserve exception: - // https://docs.python.org/3/extending/newtypes.html#finalization-and-de-allocation - Runtime.PyErr_Restore(errType, errVal, traceback); - } - } - else - { - // this also decrements ref count for _view->obj - Runtime.PyBuffer_Release(ref _view); - } - } + // this also decrements ref count for _view->obj + Runtime.PyBuffer_Release(ref _view); _exporter = null; Shape = null; @@ -266,7 +234,11 @@ private void Dispose(bool disposing) ~PyBuffer() { - Dispose(false); + if (disposedValue) + { + return; + } + Finalizer.Instance.AddFinalizedObject(this); } /// @@ -278,5 +250,10 @@ public void Dispose() Dispose(true); GC.SuppressFinalize(this); } + + public IntPtr[] GetTrackedHandles() + { + return new IntPtr[] { _view.obj }; + } } }