using System; using System.Collections; using System.IO; using System.Runtime.InteropServices; namespace Python.Runtime { /// /// The managed metatype. This object implements the type of all reflected /// types. It also provides support for single-inheritance from reflected /// managed types. /// internal class MetaType : ManagedType { private static IntPtr PyCLRMetaType; private static SlotsHolder _metaSlotsHodler; internal static readonly string[] CustomMethods = new string[] { "__instancecheck__", "__subclasscheck__", }; /// /// Metatype initialization. This bootstraps the CLR metatype to life. /// public static IntPtr Initialize() { PyCLRMetaType = TypeManager.CreateMetaType(typeof(MetaType), out _metaSlotsHodler); return PyCLRMetaType; } public static void Release() { if (Runtime.Refcount(PyCLRMetaType) > 1) { _metaSlotsHodler.ResetSlots(); } Runtime.Py_CLEAR(ref PyCLRMetaType); _metaSlotsHodler = null; } internal static void SaveRuntimeData(RuntimeDataStorage storage) { Runtime.XIncref(PyCLRMetaType); storage.PushValue(PyCLRMetaType); } internal static IntPtr RestoreRuntimeData(RuntimeDataStorage storage) { PyCLRMetaType = storage.PopValue(); _metaSlotsHodler = new SlotsHolder(PyCLRMetaType); TypeManager.InitializeSlots(PyCLRMetaType, typeof(MetaType), _metaSlotsHodler); IntPtr mdef = Marshal.ReadIntPtr(PyCLRMetaType, TypeOffset.tp_methods); foreach (var methodName in CustomMethods) { var mi = typeof(MetaType).GetMethod(methodName); ThunkInfo thunkInfo = Interop.GetThunk(mi, "BinaryFunc"); _metaSlotsHodler.KeeapAlive(thunkInfo); mdef = TypeManager.WriteMethodDef(mdef, methodName, thunkInfo.Address); } return PyCLRMetaType; } /// /// Metatype __new__ implementation. This is called to create a new /// class / type when a reflected class is subclassed. /// public static IntPtr tp_new(IntPtr tp, IntPtr args, IntPtr kw) { var len = Runtime.PyTuple_Size(args); if (len < 3) { return Exceptions.RaiseTypeError("invalid argument list"); } IntPtr name = Runtime.PyTuple_GetItem(args, 0); IntPtr bases = Runtime.PyTuple_GetItem(args, 1); IntPtr dict = Runtime.PyTuple_GetItem(args, 2); // We do not support multiple inheritance, so the bases argument // should be a 1-item tuple containing the type we are subtyping. // That type must itself have a managed implementation. We check // that by making sure its metatype is the CLR metatype. if (Runtime.PyTuple_Size(bases) != 1) { return Exceptions.RaiseTypeError("cannot use multiple inheritance with managed classes"); } IntPtr base_type = Runtime.PyTuple_GetItem(bases, 0); IntPtr mt = Runtime.PyObject_TYPE(base_type); if (!(mt == PyCLRMetaType || mt == Runtime.PyTypeType)) { return Exceptions.RaiseTypeError("invalid metatype"); } // Ensure that the reflected type is appropriate for subclassing, // disallowing subclassing of delegates, enums and array types. var cb = GetManagedObject(base_type) as ClassBase; if (cb != null) { if (!cb.CanSubclass()) { return Exceptions.RaiseTypeError("delegates, enums and array types cannot be subclassed"); } } IntPtr slots = Runtime.PyDict_GetItem(dict, PyIdentifier.__slots__); if (slots != IntPtr.Zero) { return Exceptions.RaiseTypeError("subclasses of managed classes do not support __slots__"); } // If __assembly__ or __namespace__ are in the class dictionary then create // a managed sub type. // This creates a new managed type that can be used from .net to call back // into python. if (IntPtr.Zero != dict) { Runtime.XIncref(dict); using (var clsDict = new PyDict(dict)) { if (clsDict.HasKey("__assembly__") || clsDict.HasKey("__namespace__")) { return TypeManager.CreateSubType(name, base_type, dict); } } } // otherwise just create a basic type without reflecting back into the managed side. IntPtr func = Marshal.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_new); IntPtr type = NativeCall.Call_3(func, tp, args, kw); if (type == IntPtr.Zero) { return IntPtr.Zero; } int 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); TypeManager.CopySlot(base_type, type, TypeOffset.tp_dealloc); // Hmm - the standard subtype_traverse, clear look at ob_size to // do things, so to allow gc to work correctly we need to move // our hidden handle out of ob_size. Then, in theory we can // comment this out and still not crash. TypeManager.CopySlot(base_type, type, TypeOffset.tp_traverse); TypeManager.CopySlot(base_type, type, TypeOffset.tp_clear); // for now, move up hidden handle... IntPtr gc = Marshal.ReadIntPtr(base_type, TypeOffset.magic()); Marshal.WriteIntPtr(type, TypeOffset.magic(), gc); return type; } public static IntPtr tp_alloc(IntPtr mt, int n) { IntPtr type = Runtime.PyType_GenericAlloc(mt, n); return type; } public static void tp_free(IntPtr tp) { Runtime.PyObject_GC_Del(tp); } /// /// Metatype __call__ implementation. This is needed to ensure correct /// initialization (__init__ support), because the tp_call we inherit /// from PyType_Type won't call __init__ for metatypes it doesn't know. /// public static IntPtr tp_call(IntPtr tp, IntPtr args, IntPtr kw) { IntPtr func = Marshal.ReadIntPtr(tp, TypeOffset.tp_new); if (func == IntPtr.Zero) { return Exceptions.RaiseTypeError("invalid object"); } IntPtr obj = NativeCall.Call_3(func, tp, args, kw); if (obj == IntPtr.Zero) { return IntPtr.Zero; } var init = Runtime.PyObject_GetAttr(obj, PyIdentifier.__init__); Runtime.PyErr_Clear(); if (init != IntPtr.Zero) { IntPtr result = Runtime.PyObject_Call(init, args, kw); Runtime.XDecref(init); if (result == IntPtr.Zero) { Runtime.XDecref(obj); return IntPtr.Zero; } Runtime.XDecref(result); } return obj; } /// /// Type __setattr__ implementation for reflected types. Note that this /// is slightly different than the standard setattr implementation for /// the normal Python metatype (PyTypeType). We need to look first in /// the type object of a reflected type for a descriptor in order to /// support the right setattr behavior for static fields and properties. /// public static int tp_setattro(IntPtr tp, IntPtr name, IntPtr value) { IntPtr descr = Runtime._PyType_Lookup(tp, name); if (descr != IntPtr.Zero) { IntPtr dt = Runtime.PyObject_TYPE(descr); if (dt == Runtime.PyWrapperDescriptorType || dt == Runtime.PyMethodType || typeof(ExtensionType).IsInstanceOfType(GetManagedObject(descr)) ) { IntPtr fp = Marshal.ReadIntPtr(dt, TypeOffset.tp_descr_set); if (fp != IntPtr.Zero) { return NativeCall.Int_Call_3(fp, descr, name, value); } Exceptions.SetError(Exceptions.AttributeError, "attribute is read-only"); return -1; } } int res = Runtime.PyObject_GenericSetAttr(tp, name, value); Runtime.PyType_Modified(tp); return res; } /// /// The metatype has to implement [] semantics for generic types, so /// here we just delegate to the generic type def implementation. Its /// own mp_subscript /// public static IntPtr mp_subscript(IntPtr tp, IntPtr idx) { var cb = GetManagedObject(tp) as ClassBase; if (cb != null) { return cb.type_subscript(idx); } return Exceptions.RaiseTypeError("unsubscriptable object"); } /// /// Dealloc implementation. This is called when a Python type generated /// by this metatype is no longer referenced from the Python runtime. /// 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); if ((flags & TypeFlags.Subclass) == 0) { IntPtr gc = Marshal.ReadIntPtr(tp, TypeOffset.magic()); ((GCHandle)gc).Free(); } IntPtr op = Marshal.ReadIntPtr(tp, TypeOffset.ob_type); Runtime.XDecref(op); // Delegate the rest of finalization the Python metatype. Note // that the PyType_Type implementation of tp_dealloc will call // tp_free on the type of the type being deallocated - in this // case our CLR metatype. That is why we implement tp_free. op = Marshal.ReadIntPtr(Runtime.PyTypeType, TypeOffset.tp_dealloc); NativeCall.Void_Call_1(op, tp); } private static IntPtr DoInstanceCheck(IntPtr tp, IntPtr args, bool checkType) { var cb = GetManagedObject(tp) as ClassBase; if (cb == null) { Runtime.XIncref(Runtime.PyFalse); return Runtime.PyFalse; } Runtime.XIncref(args); using (var argsObj = new PyList(args)) { if (argsObj.Length() != 1) { return Exceptions.RaiseTypeError("Invalid parameter count"); } PyObject arg = argsObj[0]; PyObject otherType; if (checkType) { otherType = arg; } else { otherType = arg.GetPythonType(); } if (Runtime.PyObject_TYPE(otherType.Handle) != PyCLRMetaType) { Runtime.XIncref(Runtime.PyFalse); return Runtime.PyFalse; } var otherCb = GetManagedObject(otherType.Handle) as ClassBase; if (otherCb == null) { Runtime.XIncref(Runtime.PyFalse); return Runtime.PyFalse; } return Converter.ToPython(cb.type.IsAssignableFrom(otherCb.type)); } } public static IntPtr __instancecheck__(IntPtr tp, IntPtr args) { return DoInstanceCheck(tp, args, false); } public static IntPtr __subclasscheck__(IntPtr tp, IntPtr args) { return DoInstanceCheck(tp, args, true); } } }