diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fbd6f7b4..0d9a79d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][]. - Python operator method will call C# operator method for supported binary and unary operators ([#1324][p1324]). - Add GetPythonThreadID and Interrupt methods in PythonEngine - Ability to implement delegates with `ref` and `out` parameters in Python, by returning the modified parameter values in a tuple. ([#1355][i1355]) +- `PyType` - a wrapper for Python type objects, that also permits creating new heap types from `TypeSpec` ### Changed - Drop support for Python 2, 3.4, and 3.5 diff --git a/Directory.Build.props b/Directory.Build.props index edc8ba513..e0cd93ede 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ Copyright (c) 2006-2020 The Contributors of the Python.NET Project pythonnet Python.NET - 7.3 + 9.0 false diff --git a/src/embed_tests/TestPyType.cs b/src/embed_tests/TestPyType.cs new file mode 100644 index 000000000..02142b782 --- /dev/null +++ b/src/embed_tests/TestPyType.cs @@ -0,0 +1,45 @@ +using System.Text; + +using NUnit.Framework; + +using Python.Runtime; +using Python.Runtime.Native; + +namespace Python.EmbeddingTest +{ + public class TestPyType + { + [OneTimeSetUp] + public void SetUp() + { + PythonEngine.Initialize(); + } + + [OneTimeTearDown] + public void Dispose() + { + PythonEngine.Shutdown(); + } + + [Test] + public void CanCreateHeapType() + { + const string name = "nÁmæ"; + const string docStr = "dÁcæ"; + + using var doc = new StrPtr(docStr, Encoding.UTF8); + var spec = new TypeSpec( + name: name, + basicSize: ObjectOffset.Size(Runtime.Runtime.PyTypeType), + slots: new TypeSpec.Slot[] { + new (TypeSlotID.tp_doc, doc.RawPointer), + }, + TypeFlags.Default | TypeFlags.HeapType + ); + + using var type = new PyType(spec); + Assert.AreEqual(name, type.GetAttr("__name__").As()); + Assert.AreEqual(docStr, type.GetAttr("__doc__").As()); + } + } +} diff --git a/src/runtime/TypeSpec.cs b/src/runtime/TypeSpec.cs new file mode 100644 index 000000000..87c0f94bc --- /dev/null +++ b/src/runtime/TypeSpec.cs @@ -0,0 +1,121 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Python.Runtime +{ + public class TypeSpec + { + public TypeSpec(string name, int basicSize, IEnumerable slots, TypeFlags flags, int itemSize = 0) + { + this.Name = name ?? throw new ArgumentNullException(nameof(name)); + this.BasicSize = basicSize; + this.Slots = slots.ToArray(); + this.Flags = flags; + this.ItemSize = itemSize; + } + public string Name { get; } + public int BasicSize { get; } + public int ItemSize { get; } + public TypeFlags Flags { get; } + public IReadOnlyList Slots { get; } + + [StructLayout(LayoutKind.Sequential)] + public struct Slot + { + public Slot(TypeSlotID id, IntPtr value) + { + ID = id; + Value = value; + } + + public TypeSlotID ID { get; } + public IntPtr Value { get; } + } + } + + public enum TypeSlotID : int + { + mp_ass_subscript = 3, + mp_length = 4, + mp_subscript = 5, + nb_absolute = 6, + nb_add = 7, + nb_and = 8, + nb_bool = 9, + nb_divmod = 10, + nb_float = 11, + nb_floor_divide = 12, + nb_index = 13, + nb_inplace_add = 14, + nb_inplace_and = 15, + nb_inplace_floor_divide = 16, + nb_inplace_lshift = 17, + nb_inplace_multiply = 18, + nb_inplace_or = 19, + nb_inplace_power = 20, + nb_inplace_remainder = 21, + nb_inplace_rshift = 22, + nb_inplace_subtract = 23, + nb_inplace_true_divide = 24, + nb_inplace_xor = 25, + nb_int = 26, + nb_invert = 27, + nb_lshift = 28, + nb_multiply = 29, + nb_negative = 30, + nb_or = 31, + nb_positive = 32, + nb_power = 33, + nb_remainder = 34, + nb_rshift = 35, + nb_subtract = 36, + nb_true_divide = 37, + nb_xor = 38, + sq_ass_item = 39, + sq_concat = 40, + sq_contains = 41, + sq_inplace_concat = 42, + sq_inplace_repeat = 43, + sq_item = 44, + sq_length = 45, + sq_repeat = 46, + tp_alloc = 47, + tp_base = 48, + tp_bases = 49, + tp_call = 50, + tp_clear = 51, + tp_dealloc = 52, + tp_del = 53, + tp_descr_get = 54, + tp_descr_set = 55, + tp_doc = 56, + tp_getattr = 57, + tp_getattro = 58, + tp_hash = 59, + tp_init = 60, + tp_is_gc = 61, + tp_iter = 62, + tp_iternext = 63, + tp_methods = 64, + tp_new = 65, + tp_repr = 66, + tp_richcompare = 67, + tp_setattr = 68, + tp_setattro = 69, + tp_str = 70, + tp_traverse = 71, + tp_members = 72, + tp_getset = 73, + tp_free = 74, + nb_matrix_multiply = 75, + nb_inplace_matrix_multiply = 76, + am_await = 77, + am_aiter = 78, + am_anext = 79, + /// New in 3.5 + tp_finalize = 80, + } +} diff --git a/src/runtime/clrobject.cs b/src/runtime/clrobject.cs index 0a352b381..0aa829ee6 100644 --- a/src/runtime/clrobject.cs +++ b/src/runtime/clrobject.cs @@ -14,7 +14,7 @@ internal CLRObject(object ob, IntPtr tp) System.Diagnostics.Debug.Assert(tp != IntPtr.Zero); IntPtr py = Runtime.PyType_GenericAlloc(tp, 0); - long flags = Util.ReadCLong(tp, TypeOffset.tp_flags); + var flags = (TypeFlags)Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Subclass) != 0) { IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.TypeDictOffset(tp)); diff --git a/src/runtime/interop.cs b/src/runtime/interop.cs index 0f56f77d9..c5958e0f7 100644 --- a/src/runtime/interop.cs +++ b/src/runtime/interop.cs @@ -344,38 +344,42 @@ public static void FreeModuleDef(IntPtr ptr) /// Note that the two values reserved for stackless have been put /// to good use as PythonNet specific flags (Managed and Subclass) /// - internal class TypeFlags + // Py_TPFLAGS_* + [Flags] + public enum TypeFlags: int { - public const int HeapType = (1 << 9); - public const int BaseType = (1 << 10); - public const int Ready = (1 << 12); - public const int Readying = (1 << 13); - public const int HaveGC = (1 << 14); + HeapType = (1 << 9), + BaseType = (1 << 10), + Ready = (1 << 12), + Readying = (1 << 13), + HaveGC = (1 << 14), // 15 and 16 are reserved for stackless - public const int HaveStacklessExtension = 0; + HaveStacklessExtension = 0, /* XXX Reusing reserved constants */ - public const int Managed = (1 << 15); // PythonNet specific - public const int Subclass = (1 << 16); // PythonNet specific - public const int HaveIndex = (1 << 17); + /// PythonNet specific + Managed = (1 << 15), + /// PythonNet specific + Subclass = (1 << 16), + HaveIndex = (1 << 17), /* Objects support nb_index in PyNumberMethods */ - public const int HaveVersionTag = (1 << 18); - public const int ValidVersionTag = (1 << 19); - public const int IsAbstract = (1 << 20); - public const int HaveNewBuffer = (1 << 21); + HaveVersionTag = (1 << 18), + ValidVersionTag = (1 << 19), + IsAbstract = (1 << 20), + HaveNewBuffer = (1 << 21), // TODO: Implement FastSubclass functions - public const int IntSubclass = (1 << 23); - public const int LongSubclass = (1 << 24); - public const int ListSubclass = (1 << 25); - public const int TupleSubclass = (1 << 26); - public const int StringSubclass = (1 << 27); - public const int UnicodeSubclass = (1 << 28); - public const int DictSubclass = (1 << 29); - public const int BaseExceptionSubclass = (1 << 30); - public const int TypeSubclass = (1 << 31); - - public const int Default = ( + IntSubclass = (1 << 23), + LongSubclass = (1 << 24), + ListSubclass = (1 << 25), + TupleSubclass = (1 << 26), + StringSubclass = (1 << 27), + UnicodeSubclass = (1 << 28), + DictSubclass = (1 << 29), + BaseExceptionSubclass = (1 << 30), + TypeSubclass = (1 << 31), + + Default = ( HaveStacklessExtension | - HaveVersionTag); + HaveVersionTag), } diff --git a/src/runtime/managedtype.cs b/src/runtime/managedtype.cs index 09e8a3be2..d3ee697fd 100644 --- a/src/runtime/managedtype.cs +++ b/src/runtime/managedtype.cs @@ -92,7 +92,7 @@ internal static ManagedType GetManagedObject(IntPtr ob) tp = ob; } - var flags = Util.ReadCLong(tp, TypeOffset.tp_flags); + var flags = (TypeFlags)Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Managed) != 0) { IntPtr op = tp == ob @@ -117,7 +117,7 @@ internal static ManagedType GetManagedObjectType(IntPtr ob) if (ob != IntPtr.Zero) { IntPtr tp = Runtime.PyObject_TYPE(ob); - var flags = Util.ReadCLong(tp, TypeOffset.tp_flags); + var flags = (TypeFlags)Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Managed) != 0) { tp = Marshal.ReadIntPtr(tp, TypeOffset.magic()); @@ -152,7 +152,7 @@ internal static bool IsManagedType(IntPtr ob) tp = ob; } - var flags = Util.ReadCLong(tp, TypeOffset.tp_flags); + var flags = (TypeFlags)Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Managed) != 0) { return true; diff --git a/src/runtime/metatype.cs b/src/runtime/metatype.cs index 36b406c7b..68dae2508 100644 --- a/src/runtime/metatype.cs +++ b/src/runtime/metatype.cs @@ -147,13 +147,13 @@ public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) return IntPtr.Zero; } - int flags = TypeFlags.Default; + var flags = TypeFlags.Default; flags |= TypeFlags.Managed; flags |= TypeFlags.HeapType; flags |= TypeFlags.BaseType; flags |= TypeFlags.Subclass; flags |= TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); + Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); TypeManager.CopySlot(base_type, type, TypeOffset.tp_dealloc); @@ -285,7 +285,7 @@ public static void tp_dealloc(IntPtr tp) { // Fix this when we dont cheat on the handle for subclasses! - var flags = Util.ReadCLong(tp, TypeOffset.tp_flags); + var flags = (TypeFlags)Util.ReadCLong(tp, TypeOffset.tp_flags); if ((flags & TypeFlags.Subclass) == 0) { IntPtr gc = Marshal.ReadIntPtr(tp, TypeOffset.magic()); diff --git a/src/runtime/native/NativeTypeSpec.cs b/src/runtime/native/NativeTypeSpec.cs new file mode 100644 index 000000000..d55b77381 --- /dev/null +++ b/src/runtime/native/NativeTypeSpec.cs @@ -0,0 +1,45 @@ +#nullable enable +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Python.Runtime.Native +{ + [StructLayout(LayoutKind.Sequential)] + struct NativeTypeSpec : IDisposable + { + public readonly StrPtr Name; + public readonly int BasicSize; + public readonly int ItemSize; + public readonly TypeFlags Flags; + public IntPtr Slots; + + public NativeTypeSpec(TypeSpec spec) + { + if (spec is null) throw new ArgumentNullException(nameof(spec)); + + this.Name = new StrPtr(spec.Name, Encoding.UTF8); + this.BasicSize = spec.BasicSize; + this.ItemSize = spec.ItemSize; + this.Flags = spec.Flags; + + unsafe + { + int slotsBytes = checked((spec.Slots.Count + 1) * Marshal.SizeOf()); + var slots = (TypeSpec.Slot*)Marshal.AllocHGlobal(slotsBytes); + for (int slotIndex = 0; slotIndex < spec.Slots.Count; slotIndex++) + slots[slotIndex] = spec.Slots[slotIndex]; + slots[spec.Slots.Count] = default; + this.Slots = (IntPtr)slots; + } + } + + public void Dispose() + { + // we have to leak the name + // this.Name.Dispose(); + Marshal.FreeHGlobal(this.Slots); + this.Slots = IntPtr.Zero; + } + } +} diff --git a/src/runtime/pytype.cs b/src/runtime/pytype.cs new file mode 100644 index 000000000..8bc08b76d --- /dev/null +++ b/src/runtime/pytype.cs @@ -0,0 +1,50 @@ +#nullable enable +using System; +using System.Runtime.InteropServices; + +using Python.Runtime.Native; + +namespace Python.Runtime +{ + public class PyType : PyObject + { + /// Creates heap type object from the . + public PyType(TypeSpec spec, PyTuple? bases = null) : base(FromSpec(spec, bases)) { } + /// Wraps an existing type object. + public PyType(PyObject o) : base(FromObject(o)) { } + + /// Checks if specified object is a Python type. + public static bool IsType(PyObject value) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + return Runtime.PyType_Check(value.obj); + } + + private static BorrowedReference FromObject(PyObject o) + { + if (o is null) throw new ArgumentNullException(nameof(o)); + if (!IsType(o)) throw new ArgumentException("object is not a type"); + + return o.Reference; + } + + private static IntPtr FromSpec(TypeSpec spec, PyTuple? bases = null) + { + if (spec is null) throw new ArgumentNullException(nameof(spec)); + + if ((spec.Flags & TypeFlags.HeapType) == 0) + throw new NotSupportedException("Only heap types are supported"); + + var nativeSpec = new NativeTypeSpec(spec); + var basesRef = bases is null ? default : bases.Reference; + var result = Runtime.PyType_FromSpecWithBases(in nativeSpec, basesRef); + + PythonException.ThrowIfIsNull(result); + + nativeSpec.Dispose(); + + return result.DangerousMoveToPointer(); + } + } +} diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs index b779c6707..caa160bcf 100644 --- a/src/runtime/runtime.cs +++ b/src/runtime/runtime.cs @@ -2006,6 +2006,8 @@ internal static IntPtr PyType_GenericAlloc(IntPtr type, long n) private static IntPtr PyType_GenericAlloc(IntPtr type, IntPtr n) => Delegates.PyType_GenericAlloc(type, n); + + internal static NewReference PyType_FromSpecWithBases(in NativeTypeSpec spec, BorrowedReference bases) => Delegates.PyType_FromSpecWithBases(in spec, bases); /// /// Finalize a type object. This should be called on all type objects to finish their initialization. This function is responsible for adding inherited slots from a type’s base class. Return 0 on success, or return -1 and sets an exception on error. @@ -2509,6 +2511,7 @@ static Delegates() PyException_SetCause = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyException_SetCause), GetUnmanagedDll(_PythonDll)); PyThreadState_SetAsyncExcLLP64 = (delegate* unmanaged[Cdecl])GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll)); PyThreadState_SetAsyncExcLP64 = (delegate* unmanaged[Cdecl])GetFunctionByName("PyThreadState_SetAsyncExc", GetUnmanagedDll(_PythonDll)); + PyType_FromSpecWithBases = (delegate* unmanaged[Cdecl])GetFunctionByName(nameof(PyType_FromSpecWithBases), GetUnmanagedDll(PythonDLL)); } static global::System.IntPtr GetUnmanagedDll(string libraryName) @@ -2786,6 +2789,7 @@ static Delegates() internal static delegate* unmanaged[Cdecl] PyThreadState_SetAsyncExcLLP64 { get; } internal static delegate* unmanaged[Cdecl] PyThreadState_SetAsyncExcLP64 { get; } internal static delegate* unmanaged[Cdecl] PyObject_GenericGetDict { get; } + internal static delegate* unmanaged[Cdecl] PyType_FromSpecWithBases { get; } } } diff --git a/src/runtime/typemanager.cs b/src/runtime/typemanager.cs index e0564b243..01aceb656 100644 --- a/src/runtime/typemanager.cs +++ b/src/runtime/typemanager.cs @@ -171,9 +171,9 @@ internal static IntPtr CreateType(Type impl) SlotsHolder slotsHolder = CreateSolotsHolder(type); InitializeSlots(type, impl, slotsHolder); - int flags = TypeFlags.Default | TypeFlags.Managed | + var flags = TypeFlags.Default | TypeFlags.Managed | TypeFlags.HeapType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); + Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); if (Runtime.PyType_Ready(type) != 0) { @@ -286,12 +286,12 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType) Runtime.XIncref(base_); } - const int flags = TypeFlags.Default + const TypeFlags flags = TypeFlags.Default | TypeFlags.Managed | TypeFlags.HeapType | TypeFlags.BaseType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); + Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); OperatorMethod.FixupSlots(type, clrType); // Leverage followup initialization from the Python runtime. Note @@ -457,11 +457,11 @@ internal static IntPtr CreateMetaType(Type impl, out SlotsHolder slotsHolder) int size = TypeOffset.magic() + IntPtr.Size; Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, new IntPtr(size)); - const int flags = TypeFlags.Default + const TypeFlags flags = TypeFlags.Default | TypeFlags.Managed | TypeFlags.HeapType | TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); + Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); // Slots will inherit from TypeType, it's not neccesary for setting them. // Inheried slots: @@ -562,11 +562,11 @@ internal static IntPtr BasicSubType(string name, IntPtr base_, Type impl) Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_); Runtime.XIncref(base_); - int flags = TypeFlags.Default; + var flags = TypeFlags.Default; flags |= TypeFlags.Managed; flags |= TypeFlags.HeapType; flags |= TypeFlags.HaveGC; - Util.WriteCLong(type, TypeOffset.tp_flags, flags); + Util.WriteCLong(type, TypeOffset.tp_flags, (int)flags); CopySlot(base_, type, TypeOffset.tp_traverse); CopySlot(base_, type, TypeOffset.tp_clear); 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