diff --git a/src/runtime/BorrowedReference.cs b/src/runtime/BorrowedReference.cs new file mode 100644 index 000000000..7dbc7a811 --- /dev/null +++ b/src/runtime/BorrowedReference.cs @@ -0,0 +1,22 @@ +namespace Python.Runtime +{ + using System; + /// + /// Represents a reference to a Python object, that is being lent, and + /// can only be safely used until execution returns to the caller. + /// + readonly ref struct BorrowedReference + { + readonly IntPtr pointer; + public bool IsNull => this.pointer == IntPtr.Zero; + + /// Gets a raw pointer to the Python object + public IntPtr DangerousGetAddress() + => this.IsNull ? throw new NullReferenceException() : this.pointer; + + BorrowedReference(IntPtr pointer) + { + this.pointer = pointer; + } + } +} diff --git a/src/runtime/NewReference.cs b/src/runtime/NewReference.cs new file mode 100644 index 000000000..3b45f821f --- /dev/null +++ b/src/runtime/NewReference.cs @@ -0,0 +1,39 @@ +namespace Python.Runtime +{ + using System; + /// + /// Represents a reference to a Python object, that is tracked by Python's reference counting. + /// + [NonCopyable] + ref struct NewReference + { + IntPtr pointer; + public bool IsNull => this.pointer == IntPtr.Zero; + + /// Gets a raw pointer to the Python object + public IntPtr DangerousGetAddress() + => this.IsNull ? throw new NullReferenceException() : this.pointer; + + /// + /// Returns wrapper around this reference, which now owns + /// the pointer. Sets the original reference to null, as it no longer owns it. + /// + public PyObject MoveToPyObject() + { + if (this.IsNull) throw new NullReferenceException(); + + var result = new PyObject(this.pointer); + this.pointer = IntPtr.Zero; + return result; + } + /// + /// Removes this reference to a Python object, and sets it to null. + /// + public void Dispose() + { + if (!this.IsNull) + Runtime.XDecref(this.pointer); + this.pointer = IntPtr.Zero; + } + } +} diff --git a/src/runtime/NonCopyableAttribute.cs b/src/runtime/NonCopyableAttribute.cs new file mode 100644 index 000000000..63d36ab42 --- /dev/null +++ b/src/runtime/NonCopyableAttribute.cs @@ -0,0 +1,6 @@ +namespace Python.Runtime +{ + using System; + [AttributeUsage(AttributeTargets.Struct)] + class NonCopyableAttribute : Attribute { } +} diff --git a/src/runtime/Python.Runtime.15.csproj b/src/runtime/Python.Runtime.15.csproj index c31d4bf91..fd4f3416a 100644 --- a/src/runtime/Python.Runtime.15.csproj +++ b/src/runtime/Python.Runtime.15.csproj @@ -129,6 +129,13 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj index 0c2f912de..c79afee3e 100644 --- a/src/runtime/Python.Runtime.csproj +++ b/src/runtime/Python.Runtime.csproj @@ -83,6 +83,7 @@ + @@ -119,6 +120,8 @@ + + diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs index 3085bb639..9d0296d47 100644 --- a/src/runtime/assemblymanager.cs +++ b/src/runtime/assemblymanager.cs @@ -145,7 +145,7 @@ internal static void UpdatePath() probed.Clear(); for (var i = 0; i < count; i++) { - IntPtr item = Runtime.PyList_GetItem(list, i); + BorrowedReference item = Runtime.PyList_GetItem(list, i); string path = Runtime.GetManagedString(item); if (path != null) { @@ -492,4 +492,4 @@ internal static Type[] GetTypes(Assembly a) } } } -} \ No newline at end of file +} diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 8a7fc1930..4e8698da1 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -292,7 +292,7 @@ internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, Meth for (int i = 0; i < pynkwargs; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(keylist, i)); - kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i); + kwargDict[keyStr] = Runtime.PyList_GetItem(valueList, i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); diff --git a/src/runtime/pydict.cs b/src/runtime/pydict.cs index 7237d1990..b396f4f3d 100644 --- a/src/runtime/pydict.cs +++ b/src/runtime/pydict.cs @@ -139,12 +139,20 @@ public PyObject Values() /// public PyObject Items() { - IntPtr items = Runtime.PyDict_Items(obj); - if (items == IntPtr.Zero) + var items = Runtime.PyDict_Items(this.obj); + try { - throw new PythonException(); + if (items.IsNull) + { + throw new PythonException(); + } + + return items.MoveToPyObject(); + } + finally + { + items.Dispose(); } - return new PyObject(items); } diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index 7748bafa9..4cceea42d 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -1509,6 +1509,8 @@ internal static IntPtr PyUnicode_FromString(string s) return PyUnicode_FromUnicode(s, s.Length); } + internal static string GetManagedString(in BorrowedReference borrowedReference) + => GetManagedString(borrowedReference.DangerousGetAddress()); /// /// Function to access the internal PyUnicode/PyString object and /// convert it to a managed string with the correct encoding. @@ -1591,7 +1593,7 @@ internal static bool PyDict_Check(IntPtr ob) internal static extern IntPtr PyDict_Values(IntPtr pointer); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr PyDict_Items(IntPtr pointer); + internal static extern NewReference PyDict_Items(IntPtr pointer); [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyDict_Copy(IntPtr pointer); @@ -1631,13 +1633,13 @@ internal static IntPtr PyList_New(long size) [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr PyList_AsTuple(IntPtr pointer); - internal static IntPtr PyList_GetItem(IntPtr pointer, long index) + internal static BorrowedReference PyList_GetItem(IntPtr pointer, long index) { return PyList_GetItem(pointer, new IntPtr(index)); } [DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr PyList_GetItem(IntPtr pointer, IntPtr index); + private static extern BorrowedReference PyList_GetItem(IntPtr pointer, IntPtr index); internal static int PyList_SetItem(IntPtr pointer, long index, IntPtr value) {