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 }; + } } } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy