/// <summary> /// 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) /// </summary> /// <param name="methodName">Method name to add to the type</param> /// <param name="func">Python callable object</param> /// <param name="typeBuilder">TypeBuilder for the new type the method/property is to be added to</param> private static void AddPythonMethod(string methodName, PyObject func, TypeBuilder typeBuilder) { if (func.HasAttr("_clr_method_name_")) { using (PyObject pyMethodName = func.GetAttr("_clr_method_name_")) { methodName = pyMethodName.ToString(); } } using (PyObject pyReturnType = func.GetAttr("_clr_return_type_")) using (PyObject pyArgTypes = func.GetAttr("_clr_arg_types_")) { var returnType = pyReturnType.AsManagedObject(typeof(Type)) as Type; if (returnType == null) { returnType = typeof(void); } if (!pyArgTypes.IsIterable()) { throw new ArgumentException("_clr_arg_types_ must be a list or tuple of CLR types"); } var argTypes = new List <Type>(); foreach (PyObject pyArgType in pyArgTypes) { 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); } // 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.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, methodName); il.Emit(OpCodes.Ldnull); // don't fall back to the base type's method il.Emit(OpCodes.Ldc_I4, argTypes.Count); il.Emit(OpCodes.Newarr, typeof(object)); il.Emit(OpCodes.Stloc_0); 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); if (argTypes[i].IsValueType) { il.Emit(OpCodes.Box, argTypes[i]); } il.Emit(OpCodes.Stelem, typeof(object)); } il.Emit(OpCodes.Ldloc_0); if (returnType == typeof(void)) { il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethodVoid")); } else { il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeMethod").MakeGenericMethod(returnType)); } il.Emit(OpCodes.Ret); } }
/// <summary> /// Python properties may have the following function attributes set to control how they're exposed: /// - _clr_property_type_ - property type (required) /// </summary> /// <param name="propertyName">Property name to add to the type</param> /// <param name="func">Python property object</param> /// <param name="typeBuilder">TypeBuilder for the new type the method/property is to be added to</param> 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 (PyObject 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 (PyObject 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); il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeGetProperty").MakeGenericMethod(propertyType)); il.Emit(OpCodes.Ret); propertyBuilder.SetGetMethod(methodBuilder); } } } if (func.HasAttr("fset")) { using (PyObject 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); il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("InvokeSetProperty").MakeGenericMethod(propertyType)); il.Emit(OpCodes.Ret); propertyBuilder.SetSetMethod(methodBuilder); } } } } }
/// <summary> /// 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. /// </summary> internal static Type CreateDerivedType(string name, Type baseType, IntPtr py_dict, string namespaceStr, string assemblyName, string moduleName = "pyRevitLabs.PythonNet.Dynamic.dll") { if (null != namespaceStr) { name = namespaceStr + "." + name; } if (null == assemblyName) { assemblyName = Assembly.GetExecutingAssembly().FullName; } ModuleBuilder moduleBuilder = GetModuleBuilder(assemblyName, moduleName); Type baseClass = baseType; var interfaces = new List <Type> { typeof(IPythonDerivedType) }; // 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("__pyobj__", typeof(CLRObject), FieldAttributes.Public); // 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 <string>(); if (py_dict != IntPtr.Zero && Runtime.PyDict_Check(py_dict)) { Runtime.XIncref(py_dict); using (var dict = new PyDict(py_dict)) using (PyObject keys = dict.Keys()) { foreach (PyObject pyKey in keys) { using (PyObject 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); } } } } } // override any virtual methods not already overridden by the properties above MethodInfo[] methods = baseType.GetMethods(); var virtualMethods = new HashSet <string>(); foreach (MethodInfo method in methods) { if (!method.Attributes.HasFlag(MethodAttributes.Virtual) | method.Attributes.HasFlag(MethodAttributes.Final)) { 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 != IntPtr.Zero && Runtime.PyDict_Check(py_dict)) { Runtime.XIncref(py_dict); using (var dict = new PyDict(py_dict)) using (PyObject keys = dict.Keys()) { foreach (PyObject pyKey in keys) { using (PyObject 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); } } } } } // 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); il.Emit(OpCodes.Call, typeof(PythonDerivedType).GetMethod("Finalize")); 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); }