[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pythonnet/pythonnet/v3.2.0/src/runtime/Types/ClassDerived.cs [Back]  [Original]

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Dynamic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;

using Python.Runtime.Native;

#pragma warning disable CS0618 // Type or member is obsolete. OK for internal use
using static Python.Runtime.PythonDerivedType;
#pragma warning restore CS0618 // Type or member is obsolete

namespace Python.Runtime
{
    /// 
    /// Managed class that provides the implementation for reflected types.
    /// Managed classes and value types are represented in Python by actual
    /// Python type objects. Each of those type objects is associated with
    /// an instance of ClassObject, which provides its implementation.
    /// 
    /// 
    /// interface used to identify which C# types were dynamically created as python subclasses
    /// 
    public interface IPythonDerivedType
    {
    }

    [Serializable]
    internal class ClassDerivedObject : ClassObject
    {
        private static Dictionary assemblyBuilders;
        private static Dictionary moduleBuilders;
        private static readonly object _buildersLock = new();

        static ClassDerivedObject()
        {
            assemblyBuilders = new Dictionary();
            moduleBuilders = new Dictionary();
        }

        public static void Reset()
        {
            // Atomic replacement of both builder caches so a concurrent
            // GetModuleBuilder cannot observe one new + one old.
            lock (_buildersLock)
            {
                assemblyBuilders = new Dictionary();
                moduleBuilders = new Dictionary();
            }
        }

        internal ClassDerivedObject(Type tp) : base(tp)
        {
        }

        protected override NewReference NewObjectToPython(object obj, BorrowedReference tp)
        {
            // Hold the wrapper across the XDecref/ToPython dance: between the
            // tp_dealloc-induced strongweak demotion and ToPython's re-upgrade,
            // the only reference to the CLRObject is the weak handle in the
            // slot, so a concurrent .NET GC can collect it and leave inst with
            // a null managed object during tp_init.
            var self = base.NewObjectToPython(obj, tp);
            SetPyObj((IPythonDerivedType)obj, self.Borrow());

            var wrapper = GetManagedObject(self.Borrow());

            Runtime.XDecref(self.Steal());

            var result = Converter.ToPython(obj, type.Value);
            GC.KeepAlive(wrapper);
            return result;
        }

        protected override void SetTypeNewSlot(BorrowedReference pyType, SlotsHolder slotsHolder)
        {
            // Python derived types rely on base tp_new and overridden __init__
        }

        public new static unsafe void tp_dealloc(NewReference ob)
        {
            var self = (CLRObject?)GetManagedObject(ob.Borrow());

            // don't let the python GC destroy this object
            Runtime.PyObject_GC_UnTrack(ob.Borrow());

            // self may be null after Shutdown begun
            if (self is not null)
            {
                // Python refcount has reached 0 but we do NOT free the PyObject:
                // the C# wrapper (via IPythonDerivedType.__pyobj__) may still
                // reference it, and a later ToPython() on that wrapper will
                // resurrect a strong handle and call _Py_NewReference.
                //
                // Demote the GCHandle from strong -> weak so the C# wrapper can
                // be collected; when it is, PyFinalize enqueues the actual
                // PyObject_GC_Del.  Until then the slot keeps the wrapper
                // reachable from Python -> C# but not the other way.
                //
                // Interlocked.Exchange: under FT (or .NET-finalizer-thread
                // races) tp_clear can hit the same slot.  Whichever thread
                // observes the original handle frees it; the loser frees the
                // weak handle it just allocated.  Without the atomic swap both
                // threads could see and free the same handle (double-free).
                GCHandle weak = GCHandle.Alloc(self, GCHandleType.Weak);
                BorrowedReference borrow = ob.Borrow();
                int offset = Util.ReadInt32(Runtime.PyObject_TYPE(borrow), Offsets.tp_clr_inst_offset);
                IntPtr* slot = (IntPtr*)(borrow.DangerousGetAddress() + offset);
                IntPtr oldRaw = System.Threading.Interlocked.Exchange(ref *slot, (IntPtr)weak);
                if (oldRaw != IntPtr.Zero)
                    ((GCHandle)oldRaw).Free();
                else
                {
                    // tp_clear already cleared the slot; put it back to zero before
                    // freeing our weak so a later read can't see a dangling handle.
                    System.Threading.Interlocked.Exchange(ref *slot, IntPtr.Zero);
                    weak.Free();
                }
            }
        }

        /// 
        /// No-op clear. Real cleanup happens in 
        /// 
        public new static int tp_clear(BorrowedReference ob) => 0;

        /// 
        /// Called from Converter.ToPython for types that are python subclasses of managed types.
        /// The referenced python object is returned instead of a new wrapper.
        /// 
        internal static NewReference ToPython(IPythonDerivedType obj)
        {
            // derived types have a __pyobj__ field that gets set to the python
            // object in the overridden constructor
            BorrowedReference self;
            try
            {
                self = GetPyObj(obj).CheckRun();
            }
            catch (RuntimeShutdownException e)
            {
                Exceptions.SetError(e);
                return default;
            }

            var result = new NewReference(self);

            // when the C# constructor creates the python object it starts as a weak
            // reference with a reference count of 0. Now we're passing this object
            // to Python the reference count needs to be incremented and the reference
            // needs to be replaced with a strong reference to stop the C# object being
            // collected while Python still has a reference to it.
            if (Runtime.Refcount(self) == 1)
            {
                Runtime._Py_NewReference(self);
                GCHandle weak = GetGCHandle(self);
                var clrObject = GetManagedObject(self);
                GCHandle gc = GCHandle.Alloc(clrObject, GCHandleType.Normal);
                SetGCHandle(self, gc);
                weak.Free();

                // now the object has a python reference it's safe for the python GC to track it
                Runtime.PyObject_GC_Track(self);
            }

            return result;
        }

        /// 
        /// Creates a new managed type derived from a base type with any virtual
        /// methods overridden to call out to python if the associated python
        /// object has overridden the method.
        /// 
        internal static Type CreateDerivedType(string name,
            Type baseType,
            IList typeInterfaces,
            BorrowedReference py_dict,
            string? namespaceStr,
            string? assemblyName,
            string moduleName = "Python.Runtime.Dynamic.dll")
        {
            // TODO: clean up
            if (null != namespaceStr)
            {
                name = namespaceStr + "." + name;
            }

            if (null == assemblyName)
            {
                assemblyName = "Python.Runtime.Dynamic";
            }

            // Reflection.Emit is not thread-safe.
            lock (_buildersLock)
            {
                return CreateDerivedTypeImpl(name, baseType, typeInterfaces, py_dict, assemblyName, moduleName);
            }
        }

        /// 
        /// Emits the CLR type for a Python subclass.  Must be called under
        /// _buildersLock since Reflection.Emit is not thread-safe.
        /// 
        private static Type CreateDerivedTypeImpl(string name,
            Type baseType,
            IList typeInterfaces,
            BorrowedReference py_dict,
            string assemblyName,
            string moduleName)
        {
            ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName);

            Type baseClass = baseType;
            var interfaces = new HashSet { typeof(IPythonDerivedType) };
            foreach(var interfaceType in typeInterfaces)
                interfaces.Add(interfaceType);

            // if the base type is an interface then use System.Object as the base class
            // and add the base type to the list of interfaces this new class will implement.
            if (baseType.IsInterface)
            {
                interfaces.Add(baseType);
                baseClass = typeof(object);
            }

            TypeBuilder typeBuilder = moduleBuilder.DefineType(name,
                TypeAttributes.Public | TypeAttributes.Class,
                baseClass,
                interfaces.ToArray());

            // add a field for storing the python object pointer
            // FIXME: fb not used
            FieldBuilder fb = typeBuilder.DefineField(PyObjName,
#pragma warning disable CS0618 // Type or member is obsolete. OK for internal use.
                                typeof(UnsafeReferenceWithRun),
#pragma warning restore CS0618 // Type or member is obsolete
                                FieldAttributes.Private);

            // override any constructors
            ConstructorInfo[] constructors = baseClass.GetConstructors();
            foreach (ConstructorInfo ctor in constructors)
            {
                AddConstructor(ctor, baseType, typeBuilder);
            }

            // Override any properties explicitly overridden in python
            var pyProperties = new HashSet();
            if (py_dict != null && Runtime.PyDict_Check(py_dict))
            {
                using var dict = new PyDict(py_dict);
                using var keys = dict.Keys();
                foreach (PyObject pyKey in keys)
                {
                    using var value = dict[pyKey];
                    if (value.HasAttr("_clr_property_type_"))
                    {
                        string propertyName = pyKey.ToString()!;
                        pyProperties.Add(propertyName);

                        // Add the property to the type
                        AddPythonProperty(propertyName, value, typeBuilder);
                    }
                    pyKey.Dispose();
                }
            }

            // override any virtual not already overridden by the properties above
            // also override any interface method.
            var methods = baseType.GetMethods().Concat(interfaces.SelectMany(x => x.GetMethods()));
            var virtualMethods = new HashSet();
            foreach (MethodInfo method in methods)
            {
                if (!method.Attributes.HasFlag(MethodAttributes.Virtual) |
                    method.Attributes.HasFlag(MethodAttributes.Final)
                    // overriding generic virtual methods is not supported
                    // so a call to that should be deferred to the base class method.
                    || method.IsGenericMethod)
                {
                    continue;
                }

                // Avoid re-entrant DLR binder recursion when Python derives from
                // DynamicObject-based types (including overrides in intermediate bases).
                if (IsDynamicObjectHookMethod(method))
                {
                    continue;
                }

                // skip if this property has already been overridden
                if ((method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
                    && pyProperties.Contains(method.Name.Substring(4)))
                {
                    continue;
                }

                // keep track of the virtual methods redirected to the python instance
                virtualMethods.Add(method.Name);

                // override the virtual method to call out to the python method, if there is one.
                AddVirtualMethod(method, baseType, typeBuilder);
            }

            // Add any additional methods and properties explicitly exposed from Python.
            if (py_dict != null && Runtime.PyDict_Check(py_dict))
            {
                using var dict = new PyDict(py_dict);
                using var keys = dict.Keys();
                foreach (PyObject pyKey in keys)
                {
                    using var value = dict[pyKey];
                    if (value.HasAttr("_clr_return_type_") && value.HasAttr("_clr_arg_types_"))
                    {
                        string methodName = pyKey.ToString()!;

                        // if this method has already been redirected to the python method skip it
                        if (virtualMethods.Contains(methodName))
                        {
                            continue;
                        }

                        // Add the method to the type
                        AddPythonMethod(methodName, value, typeBuilder);
                    }
                    pyKey.Dispose();
                }
            }

            // add the destructor so the python object created in the constructor gets destroyed
            MethodBuilder methodBuilder = typeBuilder.DefineMethod("Finalize",
                MethodAttributes.Family |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig,
                CallingConventions.Standard,
                typeof(void),
                Type.EmptyTypes);
            ILGenerator il = methodBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
            il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(PyFinalize)));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
            // Only chain to the base Finalize if it's not another pythonnet-emitted
            // type: those already call PyFinalize themselves, which would double-queue
            // the same __pyobj__ and trigger PyObject_GC_Del on freed memory.
            if (!typeof(IPythonDerivedType).IsAssignableFrom(baseClass))
            {
                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, baseClass.GetMethod("Finalize", BindingFlags.NonPublic | BindingFlags.Instance));
            }
            il.Emit(OpCodes.Ret);

            Type type = typeBuilder.CreateType();

            // scan the assembly so the newly added class can be imported
            Assembly assembly = Assembly.GetAssembly(type);
            AssemblyManager.ScanAssembly(assembly);

            // FIXME: assemblyBuilder not used
            AssemblyBuilder assemblyBuilder = assemblyBuilders[assemblyName];

            return type;
        }

        static bool IsDynamicObjectHookMethod(MethodInfo method)
        {
            MethodInfo origin = method.GetBaseDefinition();
            Type? originType = origin.DeclaringType;
            if (originType == typeof(DynamicObject))
            {
                return origin.Name switch
                {
                    nameof(DynamicObject.TryGetMember)
                    or nameof(DynamicObject.TrySetMember)
                    or nameof(DynamicObject.TryDeleteMember)
                    or nameof(DynamicObject.TryInvokeMember)
                    or nameof(DynamicObject.TryConvert)
                    or nameof(DynamicObject.TryGetIndex)
                    or nameof(DynamicObject.TrySetIndex)
                    or nameof(DynamicObject.GetDynamicMemberNames)
                    or nameof(IDynamicMetaObjectProvider.GetMetaObject) => true,
                    _ => false,
                };
            }

            if (originType == typeof(IDynamicMetaObjectProvider))
            {
                return origin.Name == nameof(IDynamicMetaObjectProvider.GetMetaObject);
            }

            return false;
        }

        /// 
        /// Add a constructor override that calls the python ctor after calling the base type constructor.
        /// 
        /// constructor to be called before calling the python ctor
        /// Python callable object
        /// TypeBuilder for the new type the ctor is to be added to
        private static void AddConstructor(ConstructorInfo ctor, Type baseType, TypeBuilder typeBuilder)
        {
            ParameterInfo[] parameters = ctor.GetParameters();
            Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray();

            // create a method for calling the original constructor
            string baseCtorName = "_" + baseType.Name + "__cinit__";
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(baseCtorName,
                MethodAttributes.Public |
                MethodAttributes.Final |
                MethodAttributes.HideBySig,
                typeof(void),
                parameterTypes);

            // emit the assembly for calling the original method using call instead of callvirt
            ILGenerator il = methodBuilder.GetILGenerator();
            il.Emit(OpCodes.Ldarg_0);
            for (var i = 0; i < parameters.Length; ++i)
            {
                il.Emit(OpCodes.Ldarg, i + 1);
            }
            il.Emit(OpCodes.Call, ctor);
            il.Emit(OpCodes.Ret);

            // override the original method with a new one that dispatches to python
            ConstructorBuilder cb = typeBuilder.DefineConstructor(MethodAttributes.Public |
                                                                  MethodAttributes.ReuseSlot |
                                                                  MethodAttributes.HideBySig,
                ctor.CallingConvention,
                parameterTypes);
            il = cb.GetILGenerator();
            il.DeclareLocal(typeof(object[]));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldstr, baseCtorName);
            il.Emit(OpCodes.Ldc_I4, parameters.Length);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Stloc_0);
            for (var i = 0; i < parameters.Length; ++i)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldarg, i + 1);
                if (parameterTypes[i].IsValueType)
                {
                    il.Emit(OpCodes.Box, parameterTypes[i]);
                }
                il.Emit(OpCodes.Stelem, typeof(object));
            }
            il.Emit(OpCodes.Ldloc_0);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
            il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeCtor)));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
            il.Emit(OpCodes.Ret);
        }

        /// 
        /// Add a virtual method override that checks for an override on the python instance
        /// and calls it, otherwise fall back to the base class method.
        /// 
        /// virtual method to be overridden
        /// Python callable object
        /// TypeBuilder for the new type the method is to be added to
        private static void AddVirtualMethod(MethodInfo method, Type baseType, TypeBuilder typeBuilder)
        {
            ParameterInfo[] parameters = method.GetParameters();
            Type[] parameterTypes = (from param in parameters select param.ParameterType).ToArray();

            // If the method isn't abstract create a method for calling the original method
            string? baseMethodName = null;
            if (!method.IsAbstract)
            {
                baseMethodName = "_" + baseType.Name + "__" + method.Name;
                MethodBuilder baseMethodBuilder = typeBuilder.DefineMethod(baseMethodName,
                    MethodAttributes.Public |
                    MethodAttributes.Final |
                    MethodAttributes.HideBySig,
                    method.ReturnType,
                    parameterTypes);

                // emit the assembly for calling the original method using call instead of callvirt
                ILGenerator baseIl = baseMethodBuilder.GetILGenerator();
                baseIl.Emit(OpCodes.Ldarg_0);
                for (var i = 0; i < parameters.Length; ++i)
                {
                    baseIl.Emit(OpCodes.Ldarg, i + 1);
                }
                baseIl.Emit(OpCodes.Call, method);
                baseIl.Emit(OpCodes.Ret);
            }

            // override the original method with a new one that dispatches to python
            MethodBuilder methodBuilder = typeBuilder.DefineMethod(method.Name,
                MethodAttributes.Public |
                MethodAttributes.ReuseSlot |
                MethodAttributes.Virtual |
                MethodAttributes.HideBySig,
                method.CallingConvention,
                method.ReturnType,
                parameterTypes);
            ILGenerator il = methodBuilder.GetILGenerator();
            il.DeclareLocal(typeof(object[]));
            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldstr, method.Name);

            // don't fall back to the base type's method if it's abstract
            if (null != baseMethodName)
            {
                il.Emit(OpCodes.Ldstr, baseMethodName);
            }
            else
            {
                il.Emit(OpCodes.Ldnull);
            }

            il.Emit(OpCodes.Ldc_I4, parameters.Length);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Stloc_0);
            for (var i = 0; i < parameters.Length; ++i)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldarg, i + 1);
                var type = parameterTypes[i];
                if (type.IsByRef)
                {
                    type = type.GetElementType();
                    il.Emit(OpCodes.Ldobj, type);
                }
                if (type.IsValueType)
                {
                    il.Emit(OpCodes.Box, type);
                }
                il.Emit(OpCodes.Stelem, typeof(object));
            }
            il.Emit(OpCodes.Ldloc_0);

            il.Emit(OpCodes.Ldtoken, method);
            il.Emit(OpCodes.Ldtoken, method.DeclaringType);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
            if (method.ReturnType == typeof(void))
            {
                il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid)));
            }
            else
            {
                il.Emit(OpCodes.Call,
                    typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(method.ReturnType));
            }
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
            CodeGenerator.GenerateMarshalByRefsBack(il, parameterTypes);
            il.Emit(OpCodes.Ret);
        }

        /// 
        /// Python method may have the following function attributes set to control how they're exposed:
        /// - _clr_return_type_    - method return type (required)
        /// - _clr_arg_types_      - list of method argument types (required)
        /// - _clr_method_name_    - method name, if different from the python method name (optional)
        /// 
        /// Method name to add to the type
        /// Python callable object
        /// TypeBuilder for the new type the method/property is to be added to
        private static void AddPythonMethod(string methodName, PyObject func, TypeBuilder typeBuilder)
        {
            const string methodNameAttribute = "_clr_method_name_";
            if (func.HasAttr(methodNameAttribute))
            {
                using PyObject pyMethodName = func.GetAttr(methodNameAttribute);
                methodName = pyMethodName.As() ?? throw new ArgumentNullException(methodNameAttribute);
            }

            using var pyReturnType = func.GetAttr("_clr_return_type_");
            using var pyArgTypes = func.GetAttr("_clr_arg_types_");
            using var pyArgTypesIter = PyIter.GetIter(pyArgTypes);
            var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type;
            if (returnType == null)
            {
                returnType = typeof(void);
            }

            var argTypes = new List();
            foreach (PyObject pyArgType in pyArgTypesIter)
            {
                var argType = pyArgType.AsManagedObject(typeof(Type)) as Type;
                if (argType == null)
                {
                    throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types");
                }
                argTypes.Add(argType);
                pyArgType.Dispose();
            }

            // add the method to call back into python
            MethodAttributes methodAttribs = MethodAttributes.Public |
                                             MethodAttributes.Virtual |
                                             MethodAttributes.ReuseSlot |
                                             MethodAttributes.HideBySig;

            MethodBuilder methodBuilder = typeBuilder.DefineMethod(methodName,
                methodAttribs,
                returnType,
                argTypes.ToArray());

            ILGenerator il = methodBuilder.GetILGenerator();

            il.DeclareLocal(typeof(object[]));
            il.DeclareLocal(typeof(RuntimeMethodHandle));
            il.DeclareLocal(typeof(RuntimeTypeHandle));

            // this
            il.Emit(OpCodes.Ldarg_0);

            // Python method to call
            il.Emit(OpCodes.Ldstr, methodName);

            // original method name
            il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method

            // create args array
            il.Emit(OpCodes.Ldc_I4, argTypes.Count);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Stloc_0);

            // fill args array
            for (var i = 0; i < argTypes.Count; ++i)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldarg, i + 1);
                var type = argTypes[i];
                if (type.IsByRef)
                {
                    type = type.GetElementType();
                    il.Emit(OpCodes.Ldobj, type);
                }
                if (type.IsValueType)
                {
                    il.Emit(OpCodes.Box, type);
                }
                il.Emit(OpCodes.Stelem, typeof(object));
            }

            // args array
            il.Emit(OpCodes.Ldloc_0);

            // method handle for the base method is null
            il.Emit(OpCodes.Ldloca_S, 1);
            il.Emit(OpCodes.Initobj, typeof(RuntimeMethodHandle));
            il.Emit(OpCodes.Ldloc_1);

            // type handle is also not required
            il.Emit(OpCodes.Ldloca_S, 2);
            il.Emit(OpCodes.Initobj, typeof(RuntimeTypeHandle));
            il.Emit(OpCodes.Ldloc_2);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only

            // invoke the method
            if (returnType == typeof(void))
            {
                il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod(nameof(InvokeMethodVoid)));
            }
            else
            {
                il.Emit(OpCodes.Call,
                    typeof(PythonDerivedType).GetMethod(nameof(InvokeMethod)).MakeGenericMethod(returnType));
            }

            CodeGenerator.GenerateMarshalByRefsBack(il, argTypes);

#pragma warning restore CS0618 // PythonDerivedType is for internal use only
            il.Emit(OpCodes.Ret);
        }

        /// 
        /// Python properties may have the following function attributes set to control how they're exposed:
        /// - _clr_property_type_     - property type (required)
        /// 
        /// Property name to add to the type
        /// Python property object
        /// TypeBuilder for the new type the method/property is to be added to
        private static void AddPythonProperty(string propertyName, PyObject func, TypeBuilder typeBuilder)
        {
            // add the method to call back into python
            MethodAttributes methodAttribs = MethodAttributes.Public |
                                             MethodAttributes.Virtual |
                                             MethodAttributes.ReuseSlot |
                                             MethodAttributes.HideBySig |
                                             MethodAttributes.SpecialName;

            using var pyPropertyType = func.GetAttr("_clr_property_type_");
            var propertyType = pyPropertyType.AsManagedObject(typeof(Type)) as Type;
            if (propertyType == null)
            {
                throw new ArgumentException("_clr_property_type must be a CLR type");
            }

            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName,
                PropertyAttributes.None,
                propertyType,
                null);

            if (func.HasAttr("fget"))
            {
                using var pyfget = func.GetAttr("fget");
                if (pyfget.IsTrue())
                {
                    MethodBuilder methodBuilder = typeBuilder.DefineMethod("get_" + propertyName,
                        methodAttribs,
                        propertyType,
                        null);

                    ILGenerator il = methodBuilder.GetILGenerator();
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldstr, propertyName);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
                    il.Emit(OpCodes.Call,
                        typeof(PythonDerivedType).GetMethod("InvokeGetProperty").MakeGenericMethod(propertyType));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
                    il.Emit(OpCodes.Ret);

                    propertyBuilder.SetGetMethod(methodBuilder);
                }
            }

            if (func.HasAttr("fset"))
            {
                using var pyset = func.GetAttr("fset");
                if (pyset.IsTrue())
                {
                    MethodBuilder methodBuilder = typeBuilder.DefineMethod("set_" + propertyName,
                        methodAttribs,
                        null,
                        new[] { propertyType });

                    ILGenerator il = methodBuilder.GetILGenerator();
                    il.Emit(OpCodes.Ldarg_0);
                    il.Emit(OpCodes.Ldstr, propertyName);
                    il.Emit(OpCodes.Ldarg_1);
#pragma warning disable CS0618 // PythonDerivedType is for internal use only
                    il.Emit(OpCodes.Call,
                        typeof(PythonDerivedType).GetMethod("InvokeSetProperty").MakeGenericMethod(propertyType));
#pragma warning restore CS0618 // PythonDerivedType is for internal use only
                    il.Emit(OpCodes.Ret);

                    propertyBuilder.SetSetMethod(methodBuilder);
                }
            }
        }

        private static ModuleBuilder GetModuleBuilder(string assemblyName, string moduleName)
        {
            var key = Tuple.Create(assemblyName, moduleName);
            // Cache check-and-create must be atomic; DefineDynamicAssembly /
            // DefineDynamicModule produce duplicate builders under contention.
            lock (_buildersLock)
            {
                if (moduleBuilders.TryGetValue(key, out ModuleBuilder? existing))
                    return existing;

                if (!assemblyBuilders.TryGetValue(assemblyName, out AssemblyBuilder? assemblyBuilder))
                {
                    assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
                        new AssemblyName(assemblyName), AssemblyBuilderAccess.Run);
                    assemblyBuilders[assemblyName] = assemblyBuilder;
                }

                var moduleBuilder = assemblyBuilder.DefineDynamicModule(moduleName);
                moduleBuilders[key] = moduleBuilder;
                return moduleBuilder;
            }
        }
    }

    /// 
    /// PythonDerivedType contains static methods used by the dynamically created
    /// derived type that allow it to call back into python from overridden virtual
    /// methods, and also handle the construction and destruction of the python
    /// object.
    /// 
    /// 
    /// This has to be public as it's called from methods on dynamically built classes
    /// potentially in other assemblies.
    /// 
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete(Util.InternalUseOnly)]
    public class PythonDerivedType
    {
        internal const string PyObjName = "__pyobj__";
        internal const BindingFlags PyObjFlags = BindingFlags.Instance | BindingFlags.NonPublic;

        /// 
        /// This is the implementation of the overridden methods in the derived
        /// type. It looks for a python method with the same name as the method
        /// on the managed base class and if it exists and isn't the managed
        /// method binding (i.e. it has been overridden in the derived python
        /// class) it calls it, otherwise it calls the base method.
        /// 
        public static T? InvokeMethod(IPythonDerivedType obj, string methodName, string origMethodName,
            object[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle)
        {
            var self = GetPyObj(obj);

            if (null != self.Ref)
            {
                var disposeList = new List();
                PyGILState gs = Runtime.PyGILState_Ensure();
                try
                {
                    using var pyself = new PyObject(self.CheckRun());
                    using PyObject method = pyself.GetAttr(methodName, Runtime.None);
                    if (method.Reference != Runtime.PyNone)
                    {
                        // if the method hasn't been overridden then it will be a managed object
                        ManagedType? managedMethod = ManagedType.GetManagedObject(method.Reference);
                        if (null == managedMethod)
                        {
                            var pyargs = new PyObject[args.Length];
                            for (var i = 0; i < args.Length; ++i)
                            {
                                pyargs[i] = Converter.ToPythonImplicit(args[i]).MoveToPyObject();
                                disposeList.Add(pyargs[i]);
                            }

                            PyObject py_result = method.Invoke(pyargs);
                            var clrMethod = methodHandle != default
                                ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle)
                                : null;
                            PyTuple? result_tuple = MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 1);
                            return result_tuple is not null
                                ? result_tuple[0].As()
                                : py_result.As();
                        }
                    }
                }
                finally
                {
                    foreach (PyObject x in disposeList)
                    {
                        x?.Dispose();
                    }
                    Runtime.PyGILState_Release(gs);
                }
            }

            if (origMethodName == null)
            {
                throw new NotImplementedException("Python object does not have a '" + methodName + "' method");
            }

            return (T)obj.GetType().InvokeMember(origMethodName,
                BindingFlags.InvokeMethod,
                null,
                obj,
                args);
        }

        public static void InvokeMethodVoid(IPythonDerivedType obj, string methodName, string origMethodName,
            object?[] args, RuntimeMethodHandle methodHandle, RuntimeTypeHandle declaringTypeHandle)
        {
            var self = GetPyObj(obj);
            if (null != self.Ref)
            {
                var disposeList = new List();
                PyGILState gs = Runtime.PyGILState_Ensure();
                try
                {
                    using var pyself = new PyObject(self.CheckRun());
                    using PyObject method = pyself.GetAttr(methodName, Runtime.None);
                    if (method.Reference != Runtime.None)
                    {
                        // if the method hasn't been overridden then it will be a managed object
                        ManagedType? managedMethod = ManagedType.GetManagedObject(method);
                        if (null == managedMethod)
                        {
                            var pyargs = new PyObject[args.Length];
                            for (var i = 0; i < args.Length; ++i)
                            {
                                pyargs[i] = Converter.ToPythonImplicit(args[i]).MoveToPyObject();
                                disposeList.Add(pyargs[i]);
                            }

                            PyObject py_result = method.Invoke(pyargs);
                            var clrMethod = methodHandle != default
                                ? MethodBase.GetMethodFromHandle(methodHandle, declaringTypeHandle)
                                : null;
                            MarshalByRefsBack(args, clrMethod, py_result, outsOffset: 0);
                            return;
                        }
                    }
                }
                finally
                {
                    foreach (PyObject x in disposeList)
                    {
                        x?.Dispose();
                    }
                    Runtime.PyGILState_Release(gs);
                }
            }

            if (origMethodName == null)
            {
                throw new NotImplementedException($"Python object does not have a '{methodName}' method");
            }

            obj.GetType().InvokeMember(origMethodName,
                BindingFlags.InvokeMethod,
                null,
                obj,
                args);
        }

        /// 
        /// If the method has byref arguments, reinterprets Python return value
        /// as a tuple of new values for those arguments, and updates corresponding
        /// elements of  array.
        /// 
        private static PyTuple? MarshalByRefsBack(object?[] args, MethodBase? method, PyObject pyResult, int outsOffset)
        {
            if (method is null) return null;

            var parameters = method.GetParameters();
            PyTuple? outs = null;
            int byrefIndex = 0;
            for (int i = 0; i < parameters.Length; ++i)
            {
                Type type = parameters[i].ParameterType;
                if (!type.IsByRef)
                {
                    continue;
                }

                type = type.GetElementType();

                if (outs is null)
                {
                    outs = new PyTuple(pyResult);
                    pyResult.Dispose();
                }

                args[i] = outs[byrefIndex + outsOffset].AsManagedObject(type);
                byrefIndex++;
            }
            if (byrefIndex > 0 && outs!.Length() > byrefIndex + outsOffset)
                throw new ArgumentException("Too many output parameters");

            return outs;
        }

        public static T? InvokeGetProperty(IPythonDerivedType obj, string propertyName)
        {
            var self = GetPyObj(obj);

            if (null == self.Ref)
            {
                throw new NullReferenceException("Instance must be specified when getting a property");
            }

            PyGILState gs = Runtime.PyGILState_Ensure();
            try
            {
                using var pyself = new PyObject(self.CheckRun());
                using var pyvalue = pyself.GetAttr(propertyName);
                return pyvalue.As();
            }
            finally
            {
                Runtime.PyGILState_Release(gs);
            }
        }

        public static void InvokeSetProperty(IPythonDerivedType obj, string propertyName, T value)
        {
            var self = GetPyObj(obj);

            if (null == self.Ref)
            {
                throw new NullReferenceException("Instance must be specified when setting a property");
            }

            PyGILState gs = Runtime.PyGILState_Ensure();
            try
            {
                using var pyself = new PyObject(self.CheckRun());
                using var pyvalue = Converter.ToPythonImplicit(value).MoveToPyObject();
                pyself.SetAttr(propertyName, pyvalue);
            }
            finally
            {
                Runtime.PyGILState_Release(gs);
            }
        }

        public static void InvokeCtor(IPythonDerivedType obj, string origCtorName, object[] args)
        {
            var selfRef = GetPyObj(obj);
            if (selfRef.Ref == null)
            {
                // this might happen when the object is created from .NET
                using var _ = Py.GIL();
                // In the end we decrement the python object's reference count.
                // This doesn't actually destroy the object, it just sets the reference to this object
                // to be a weak reference and it will be destroyed when the C# object is destroyed.
                using var self = CLRObject.GetReference(obj, obj.GetType());
                SetPyObj(obj, self.Borrow());
            }

            // call the base constructor
            obj.GetType().InvokeMember(origCtorName,
                BindingFlags.InvokeMethod,
                null,
                obj,
                args);
        }

        public static void PyFinalize(IPythonDerivedType obj)
        {
            // the C# object is being destroyed which must mean there are no more
            // references to the Python object as well
            var self = GetPyObj(obj);
            Finalizer.Instance.AddDerivedFinalizedObject(ref self.RawObj, self.Run);
        }

        internal static void Finalize(IntPtr derived)
        {
            var @ref = NewReference.DangerousFromPointer(derived);

            ClassBase.tp_clear(@ref.Borrow());

            var type = Runtime.PyObject_TYPE(@ref.Borrow());

            if (!Runtime.HostedInPython || Runtime.TypeManagerInitialized)
            {
                // rare case when it's needed
                // matches correspdonging PyObject_GC_UnTrack
                // in ClassDerivedObject.tp_dealloc
                Runtime.PyObject_GC_Del(@ref.Steal());

                // must decref our type
                Runtime.XDecref(StolenReference.DangerousFromPointer(type.DangerousGetAddress()));
            }
        }

        internal static FieldInfo? GetPyObjField(Type type) => type.GetField(PyObjName, PyObjFlags);

        internal static UnsafeReferenceWithRun GetPyObj(IPythonDerivedType obj)
        {
            FieldInfo fi = GetPyObjField(obj.GetType())!;
            return (UnsafeReferenceWithRun)fi.GetValue(obj);
        }

        internal static void SetPyObj(IPythonDerivedType obj, BorrowedReference pyObj)
        {
            FieldInfo fi = GetPyObjField(obj.GetType())!;
            fi.SetValue(obj, new UnsafeReferenceWithRun(pyObj));
        }
    }
}

Web Proxy Viewer  |  New URL  |  Original Page