private static ClassInfo GetClassInfo(Type type) { var ci = new ClassInfo(); var methods = new Hashtable(); ArrayList list; MethodInfo meth; ManagedType ob; string name; object item; Type tp; int i, n; MemberInfo[] info = type.GetMembers(BindingFlags); var local = new Hashtable(); var items = new ArrayList(); MemberInfo m; // Loop through once to find out which names are declared for (i = 0; i < info.Length; i++) { m = info[i]; if (m.DeclaringType == type) { local[m.Name] = 1; } } // only [Flags] enums support bitwise operations if (type.IsEnum && type.IsFlagsEnum()) { var opsImpl = typeof(EnumOps <>).MakeGenericType(type); foreach (var op in opsImpl.GetMethods(OpsHelper.BindingFlags)) { local[op.Name] = 1; } info = info.Concat(opsImpl.GetMethods(OpsHelper.BindingFlags)).ToArray(); } // Now again to filter w/o losing overloaded member info for (i = 0; i < info.Length; i++) { m = info[i]; if (local[m.Name] != null) { items.Add(m); } } if (type.IsInterface) { // Interface inheritance seems to be a different animal: // more contractual, less structural. Thus, a Type that // represents an interface that inherits from another // interface does not return the inherited interface's // methods in GetMembers. For example ICollection inherits // from IEnumerable, but ICollection's GetMemebers does not // return GetEnumerator. // // Not sure if this is the correct way to fix this, but it // seems to work. Thanks to Bruce Dodson for the fix. Type[] inheritedInterfaces = type.GetInterfaces(); for (i = 0; i < inheritedInterfaces.Length; ++i) { Type inheritedType = inheritedInterfaces[i]; MemberInfo[] imembers = inheritedType.GetMembers(BindingFlags); for (n = 0; n < imembers.Length; n++) { m = imembers[n]; if (local[m.Name] == null) { items.Add(m); } } } // All interface implementations inherit from Object, // but GetMembers don't return them either. var objFlags = BindingFlags.Public | BindingFlags.Instance; foreach (var mi in typeof(object).GetMembers(objFlags)) { if (local[mi.Name] == null) { items.Add(mi); } } } for (i = 0; i < items.Count; i++) { var mi = (MemberInfo)items[i]; switch (mi.MemberType) { case MemberTypes.Method: meth = (MethodInfo)mi; if (!ShouldBindMethod(meth)) { continue; } name = meth.Name; item = methods[name]; if (item == null) { item = methods[name] = new ArrayList(); } list = (ArrayList)item; list.Add(meth); continue; case MemberTypes.Property: var pi = (PropertyInfo)mi; if (!ShouldBindProperty(pi)) { continue; } // Check for indexer ParameterInfo[] args = pi.GetIndexParameters(); if (args.GetLength(0) > 0) { Indexer idx = ci.indexer; if (idx == null) { ci.indexer = new Indexer(); idx = ci.indexer; } idx.AddProperty(pi); continue; } ob = new PropertyObject(pi); ci.members[pi.Name] = ob; continue; case MemberTypes.Field: var fi = (FieldInfo)mi; if (!ShouldBindField(fi)) { continue; } ob = new FieldObject(fi); ci.members[mi.Name] = ob; continue; case MemberTypes.Event: var ei = (EventInfo)mi; if (!ShouldBindEvent(ei)) { continue; } ob = new EventObject(ei); ci.members[ei.Name] = ob; continue; case MemberTypes.NestedType: tp = (Type)mi; if (!(tp.IsNestedPublic || tp.IsNestedFamily || tp.IsNestedFamORAssem)) { continue; } // Note the given instance might be uninitialized ob = GetClass(tp); if (ob.pyHandle == IntPtr.Zero && ob is ClassObject) { ob.pyHandle = ob.tpHandle = TypeManager.GetOrCreateClass(tp).Handle; } Debug.Assert(ob.pyHandle != IntPtr.Zero); // GetClass returns a Borrowed ref. ci.members owns the reference. ob.IncrRefCount(); ci.members[mi.Name] = ob; continue; } } IDictionaryEnumerator iter = methods.GetEnumerator(); while (iter.MoveNext()) { name = (string)iter.Key; list = (ArrayList)iter.Value; var mlist = (MethodInfo[])list.ToArray(typeof(MethodInfo)); ob = new MethodObject(type, name, mlist); ci.members[name] = ob; if (mlist.Any(OperatorMethod.IsOperatorMethod)) { string pyName = OperatorMethod.GetPyMethodName(name); string pyNameReverse = OperatorMethod.ReversePyMethodName(pyName); OperatorMethod.FilterMethods(mlist, out var forwardMethods, out var reverseMethods); // Only methods where the left operand is the declaring type. if (forwardMethods.Length > 0) { ci.members[pyName] = new MethodObject(type, name, forwardMethods); } // Only methods where only the right operand is the declaring type. if (reverseMethods.Length > 0) { ci.members[pyNameReverse] = new MethodObject(type, name, reverseMethods); } } } if (ci.indexer == null && type.IsClass) { // Indexer may be inherited. var parent = type.BaseType; while (parent != null && ci.indexer == null) { foreach (var prop in parent.GetProperties()) { var args = prop.GetIndexParameters(); if (args.GetLength(0) > 0) { ci.indexer = new Indexer(); ci.indexer.AddProperty(prop); break; } } parent = parent.BaseType; } } return(ci); }
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { // loop to find match, return invoker w/ or /wo error MethodBase[] _methods = null; var kwargDict = new Dictionary <string, IntPtr>(); if (kw != IntPtr.Zero) { var pynkwargs = (int)Runtime.PyDict_Size(kw); IntPtr keylist = Runtime.PyDict_Keys(kw); IntPtr valueList = Runtime.PyDict_Values(kw); for (int i = 0; i < pynkwargs; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(new BorrowedReference(keylist), i)); kwargDict[keyStr] = Runtime.PyList_GetItem(new BorrowedReference(valueList), i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); } var pynargs = (int)Runtime.PyTuple_Size(args); var isGeneric = false; if (info != null) { _methods = new MethodBase[1]; _methods.SetValue(info, 0); } else { _methods = GetMethods(); } var argMatchedMethods = new List <MatchedMethod>(_methods.Length); // TODO: Clean up foreach (MethodBase mi in _methods) { if (mi.IsGenericMethod) { isGeneric = true; } ParameterInfo[] pi = mi.GetParameters(); ArrayList defaultArgList; bool paramsArray; int kwargsMatched; int defaultsNeeded; bool isOperator = OperatorMethod.IsOperatorMethod(mi); int clrnargs = pi.Length; // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. isOperator = isOperator && pynargs == clrnargs - 1; if (!MatchesArgumentCount(pynargs, pi, kwargDict, out paramsArray, out defaultArgList, out kwargsMatched, out defaultsNeeded) && !isOperator) { continue; } // Preprocessing pi to remove either the first or second argument. bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. if (isOperator && !isReverse) { // The first Python arg is the right operand, while the bound instance is the left. // We need to skip the first (left operand) CLR argument. pi = pi.Skip(1).ToArray(); } else if (isOperator && isReverse) { // The first Python arg is the left operand. // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); } var outs = 0; var margs = TryConvertArguments(pi, paramsArray, args, pynargs, kwargDict, defaultArgList, needsResolution: _methods.Length > 1, // If there's more than one possible match. outs: out outs); if (margs == null) { continue; } if (isOperator) { if (inst != IntPtr.Zero) { if (ManagedType.GetManagedObject(inst) is CLRObject co) { bool isUnary = pynargs == 0; // Postprocessing to extend margs. var margsTemp = isUnary ? new object[1] : new object[2]; // If reverse, the bound instance is the right operand. int boundOperandIndex = isReverse ? 1 : 0; // If reverse, the passed instance is the left operand. int passedOperandIndex = isReverse ? 0 : 1; margsTemp[boundOperandIndex] = co.inst; if (!isUnary) { margsTemp[passedOperandIndex] = margs[0]; } margs = margsTemp; } else { break; } } } var matchedMethod = new MatchedMethod(kwargsMatched, defaultsNeeded, margs, outs, mi); argMatchedMethods.Add(matchedMethod); } if (argMatchedMethods.Count > 0) { var bestKwargMatchCount = argMatchedMethods.Max(x => x.KwargsMatched); var fewestDefaultsRequired = argMatchedMethods.Where(x => x.KwargsMatched == bestKwargMatchCount).Min(x => x.DefaultsNeeded); int bestCount = 0; int bestMatchIndex = -1; for (int index = 0; index < argMatchedMethods.Count; index++) { var testMatch = argMatchedMethods[index]; if (testMatch.DefaultsNeeded == fewestDefaultsRequired && testMatch.KwargsMatched == bestKwargMatchCount) { bestCount++; if (bestMatchIndex == -1) { bestMatchIndex = index; } } } if (bestCount > 1 && fewestDefaultsRequired > 0) { // Best effort for determining method to match on gives multiple possible // matches and we need at least one default argument - bail from this point return(null); } // If we're here either: // (a) There is only one best match // (b) There are multiple best matches but none of them require // default arguments // in the case of (a) we're done by default. For (b) regardless of which // method we choose, all arguments are specified _and_ can be converted // from python to C# so picking any will suffice MatchedMethod bestMatch = argMatchedMethods[bestMatchIndex]; var margs = bestMatch.ManagedArgs; var outs = bestMatch.Outs; var mi = bestMatch.Method; object target = null; if (!mi.IsStatic && inst != IntPtr.Zero) { //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst); // InvalidCastException: Unable to cast object of type // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject' var co = ManagedType.GetManagedObject(inst) as CLRObject; // Sanity check: this ensures a graceful exit if someone does // something intentionally wrong like call a non-static method // on the class rather than on an instance of the class. // XXX maybe better to do this before all the other rigmarole. if (co == null) { return(null); } target = co.inst; } return(new Binding(mi, target, margs, outs)); } // We weren't able to find a matching method but at least one // is a generic method and info is null. That happens when a generic // method was not called using the [] syntax. Let's introspect the // type of the arguments and use it to construct the correct method. if (isGeneric && info == null && methodinfo != null) { Type[] types = Runtime.PythonArgsToTypeArray(args, true); MethodInfo mi = MatchParameters(methodinfo, types); return(Bind(inst, args, kw, mi, null)); } return(null); }
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { // Relevant function variables used post conversion var isGeneric = false; Binding bindingUsingImplicitConversion = null; // If we have KWArgs create dictionary and collect them Dictionary <string, IntPtr> kwArgDict = null; if (kw != IntPtr.Zero) { var pyKwArgsCount = (int)Runtime.PyDict_Size(kw); kwArgDict = new Dictionary <string, IntPtr>(pyKwArgsCount); IntPtr keylist = Runtime.PyDict_Keys(kw); IntPtr valueList = Runtime.PyDict_Values(kw); for (int i = 0; i < pyKwArgsCount; ++i) { var keyStr = Runtime.GetManagedString(Runtime.PyList_GetItem(new BorrowedReference(keylist), i)); kwArgDict[keyStr] = Runtime.PyList_GetItem(new BorrowedReference(valueList), i).DangerousGetAddress(); } Runtime.XDecref(keylist); Runtime.XDecref(valueList); } // Fetch our methods we are going to attempt to match and bind too. var methods = info == null?GetMethods() : new List <MethodInformation>(1) { new MethodInformation(info, info.GetParameters()) }; foreach (var methodInformation in methods) { // Relevant method variables var mi = methodInformation.MethodBase; var pi = methodInformation.ParameterInfo; isGeneric = mi.IsGenericMethod; int pyArgCount = (int)Runtime.PyTuple_Size(args); // Special case for operators bool isOperator = OperatorMethod.IsOperatorMethod(mi); // Binary operator methods will have 2 CLR args but only one Python arg // (unary operators will have 1 less each), since Python operator methods are bound. isOperator = isOperator && pyArgCount == pi.Length - 1; bool isReverse = isOperator && OperatorMethod.IsReverse((MethodInfo)mi); // Only cast if isOperator. if (isReverse && OperatorMethod.IsComparisonOp((MethodInfo)mi)) { continue; // Comparison operators in Python have no reverse mode. } // Preprocessing pi to remove either the first or second argument. if (isOperator && !isReverse) { // The first Python arg is the right operand, while the bound instance is the left. // We need to skip the first (left operand) CLR argument. pi = pi.Skip(1).ToArray(); } else if (isOperator && isReverse) { // The first Python arg is the left operand. // We need to take the first CLR argument. pi = pi.Take(1).ToArray(); } // Must be done after IsOperator section int clrArgCount = pi.Length; if (CheckMethodArgumentsMatch(clrArgCount, pyArgCount, kwArgDict, pi, out bool paramsArray, out int arrayStart, out ArrayList defaultArgList)) { var outs = 0; var margs = new object[clrArgCount]; arrayStart = paramsArray ? pi.Length - 1 : -1; var usedImplicitConversion = false; // Conversion loop for each parameter for (int paramIndex = 0; paramIndex < clrArgCount; paramIndex++) { IntPtr op = IntPtr.Zero; // Python object to be converted; not yet set var parameter = pi[paramIndex]; // Clr parameter we are targeting object arg; // Python -> Clr argument // Check our KWargs for this parameter bool hasNamedParam = kwArgDict == null ? false : kwArgDict.TryGetValue(parameter.Name, out op); bool isNewReference = false; // Check if we are going to use default if (paramIndex >= pyArgCount && !(hasNamedParam || (paramsArray && paramIndex == arrayStart))) { if (defaultArgList != null) { margs[paramIndex] = defaultArgList[paramIndex - pyArgCount]; } continue; } // At this point, if op is IntPtr.Zero we don't have a KWArg and are not using default if (op == IntPtr.Zero) { if (arrayStart == paramIndex) { op = HandleParamsArray(args, arrayStart, pyArgCount, out isNewReference); } else { op = Runtime.PyTuple_GetItem(args, paramIndex); } } // this logic below handles cases when multiple overloading methods // are ambiguous, hence comparison between Python and CLR types // is necessary Type clrtype = null; IntPtr pyoptype; if (methods.Count > 1) { pyoptype = IntPtr.Zero; pyoptype = Runtime.PyObject_Type(op); Exceptions.Clear(); if (pyoptype != IntPtr.Zero) { clrtype = Converter.GetTypeByAlias(pyoptype); } Runtime.XDecref(pyoptype); } if (clrtype != null) { var typematch = false; if ((parameter.ParameterType != typeof(object)) && (parameter.ParameterType != clrtype)) { IntPtr pytype = Converter.GetPythonTypeByAlias(parameter.ParameterType); pyoptype = Runtime.PyObject_Type(op); Exceptions.Clear(); if (pyoptype != IntPtr.Zero) { if (pytype != pyoptype) { typematch = false; } else { typematch = true; clrtype = parameter.ParameterType; } } if (!typematch) { // this takes care of nullables var underlyingType = Nullable.GetUnderlyingType(parameter.ParameterType); if (underlyingType == null) { underlyingType = parameter.ParameterType; } // this takes care of enum values TypeCode argtypecode = Type.GetTypeCode(underlyingType); TypeCode paramtypecode = Type.GetTypeCode(clrtype); if (argtypecode == paramtypecode) { typematch = true; clrtype = parameter.ParameterType; } // accepts non-decimal numbers in decimal parameters if (underlyingType == typeof(decimal)) { clrtype = parameter.ParameterType; typematch = Converter.ToManaged(op, clrtype, out arg, false); } // this takes care of implicit conversions var opImplicit = parameter.ParameterType.GetMethod("op_Implicit", new[] { clrtype }); if (opImplicit != null) { usedImplicitConversion = typematch = opImplicit.ReturnType == parameter.ParameterType; clrtype = parameter.ParameterType; } } Runtime.XDecref(pyoptype); if (!typematch) { margs = null; break; } } else { clrtype = parameter.ParameterType; } } else { clrtype = parameter.ParameterType; } if (parameter.IsOut || clrtype.IsByRef) { outs++; } if (!Converter.ToManaged(op, clrtype, out arg, false)) { Exceptions.Clear(); margs = null; break; } if (isNewReference) { // TODO: is this a bug? Should this happen even if the conversion fails? // GetSlice() creates a new reference but GetItem() // returns only a borrow reference. Runtime.XDecref(op); } margs[paramIndex] = arg; } if (margs == null) { continue; } if (isOperator) { if (inst != IntPtr.Zero) { if (ManagedType.GetManagedObject(inst) is CLRObject co) { bool isUnary = pyArgCount == 0; // Postprocessing to extend margs. var margsTemp = isUnary ? new object[1] : new object[2]; // If reverse, the bound instance is the right operand. int boundOperandIndex = isReverse ? 1 : 0; // If reverse, the passed instance is the left operand. int passedOperandIndex = isReverse ? 0 : 1; margsTemp[boundOperandIndex] = co.inst; if (!isUnary) { margsTemp[passedOperandIndex] = margs[0]; } margs = margsTemp; } else { continue; } } } object target = null; if (!mi.IsStatic && inst != IntPtr.Zero) { //CLRObject co = (CLRObject)ManagedType.GetManagedObject(inst); // InvalidCastException: Unable to cast object of type // 'Python.Runtime.ClassObject' to type 'Python.Runtime.CLRObject' var co = ManagedType.GetManagedObject(inst) as CLRObject; // Sanity check: this ensures a graceful exit if someone does // something intentionally wrong like call a non-static method // on the class rather than on an instance of the class. // XXX maybe better to do this before all the other rigmarole. if (co == null) { return(null); } target = co.inst; } var binding = new Binding(mi, target, margs, outs); if (usedImplicitConversion) { // lets just keep the first binding using implicit conversion // this is to respect method order/precedence if (bindingUsingImplicitConversion == null) { // in this case we will not return the binding yet in case there is a match // which does not use implicit conversions, which will return directly bindingUsingImplicitConversion = binding; } } else { return(binding); } } } // if we generated a binding using implicit conversion return it if (bindingUsingImplicitConversion != null) { return(bindingUsingImplicitConversion); } // We weren't able to find a matching method but at least one // is a generic method and info is null. That happens when a generic // method was not called using the [] syntax. Let's introspect the // type of the arguments and use it to construct the correct method. if (isGeneric && info == null && methodinfo != null) { Type[] types = Runtime.PythonArgsToTypeArray(args, true); MethodInfo mi = MatchParameters(methodinfo, types); return(Bind(inst, args, kw, mi, null)); } return(null); }
internal static IntPtr CreateType(ManagedType impl, Type clrType) { // Cleanup the type name to get rid of funny nested type names. string name = $"clr.{clrType.FullName}"; int i = name.LastIndexOf('+'); if (i > -1) { name = name.Substring(i + 1); } i = name.LastIndexOf('.'); if (i > -1) { name = name.Substring(i + 1); } IntPtr base_ = IntPtr.Zero; int ob_size = ObjectOffset.Size(Runtime.PyTypeType); // XXX Hack, use a different base class for System.Exception // Python 2.5+ allows new style class exceptions but they *must* // subclass BaseException (or better Exception). if (typeof(Exception).IsAssignableFrom(clrType)) { ob_size = ObjectOffset.Size(Exceptions.Exception); } int tp_dictoffset = ob_size + ManagedDataOffsets.ob_dict; if (clrType == typeof(Exception)) { base_ = Exceptions.Exception; } else if (clrType.BaseType != null) { ClassBase bc = ClassManager.GetClass(clrType.BaseType); base_ = bc.pyHandle; } IntPtr type = AllocateTypeObject(name, Runtime.PyCLRMetaType); Marshal.WriteIntPtr(type, TypeOffset.ob_type, Runtime.PyCLRMetaType); Runtime.XIncref(Runtime.PyCLRMetaType); Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size); Marshal.WriteIntPtr(type, TypeOffset.tp_itemsize, IntPtr.Zero); Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, (IntPtr)tp_dictoffset); // we want to do this after the slot stuff above in case the class itself implements a slot method SlotsHolder slotsHolder = CreateSolotsHolder(type); InitializeSlots(type, impl.GetType(), slotsHolder); if (Marshal.ReadIntPtr(type, TypeOffset.mp_length) == IntPtr.Zero && mp_length_slot.CanAssign(clrType)) { InitializeSlot(type, TypeOffset.mp_length, mp_length_slot.Method, slotsHolder); } if (!typeof(IEnumerable).IsAssignableFrom(clrType) && !typeof(IEnumerator).IsAssignableFrom(clrType)) { // The tp_iter slot should only be set for enumerable types. Marshal.WriteIntPtr(type, TypeOffset.tp_iter, IntPtr.Zero); } // Only set mp_subscript and mp_ass_subscript for types with indexers if (impl is ClassBase cb) { if (!(impl is ArrayObject)) { if (cb.indexer == null || !cb.indexer.CanGet) { Marshal.WriteIntPtr(type, TypeOffset.mp_subscript, IntPtr.Zero); } if (cb.indexer == null || !cb.indexer.CanSet) { Marshal.WriteIntPtr(type, TypeOffset.mp_ass_subscript, IntPtr.Zero); } } } else { Marshal.WriteIntPtr(type, TypeOffset.mp_subscript, IntPtr.Zero); Marshal.WriteIntPtr(type, TypeOffset.mp_ass_subscript, IntPtr.Zero); } if (base_ != IntPtr.Zero) { Marshal.WriteIntPtr(type, TypeOffset.tp_base, base_); Runtime.XIncref(base_); } const int flags = TypeFlags.Default | TypeFlags.Managed | TypeFlags.HeapType | TypeFlags.BaseType | TypeFlags.HaveGC; Util.WriteCLong(type, TypeOffset.tp_flags, flags); OperatorMethod.FixupSlots(type, clrType); // Leverage followup initialization from the Python runtime. Note // that the type of the new type must PyType_Type at the time we // call this, else PyType_Ready will skip some slot initialization. if (Runtime.PyType_Ready(type) != 0) { throw new PythonException(); } IntPtr dict = Marshal.ReadIntPtr(type, TypeOffset.tp_dict); string mn = clrType.Namespace ?? ""; IntPtr mod = Runtime.PyString_FromString(mn); Runtime.PyDict_SetItem(dict, PyIdentifier.__module__, mod); Runtime.XDecref(mod); // Hide the gchandle of the implementation in a magic type slot. GCHandle gc = impl.AllocGCHandle(); Marshal.WriteIntPtr(type, TypeOffset.magic(), (IntPtr)gc); // Set the handle attributes on the implementing instance. impl.tpHandle = type; impl.pyHandle = type; //DebugUtil.DumpType(type); return(type); }
/// <summary> /// Return a precedence value for a particular Type object. /// </summary> internal static int ArgPrecedence(Type t, MethodInformation mi) { Type objectType = typeof(object); if (t == objectType) { return(3000); } if (t.IsAssignableFrom(typeof(PyObject)) && !OperatorMethod.IsOperatorMethod(mi.MethodBase)) { return(-1); } TypeCode tc = Type.GetTypeCode(t); // TODO: Clean up switch (tc) { case TypeCode.Object: return(1); case TypeCode.UInt64: return(10); case TypeCode.UInt32: return(11); case TypeCode.UInt16: return(12); case TypeCode.Int64: return(13); case TypeCode.Int32: return(14); case TypeCode.Int16: return(15); case TypeCode.Char: return(16); case TypeCode.SByte: return(17); case TypeCode.Byte: return(18); case TypeCode.Single: return(20); case TypeCode.Double: return(21); case TypeCode.String: return(30); case TypeCode.Boolean: return(40); } if (t.IsArray) { Type e = t.GetElementType(); if (e == objectType) { return(2500); } return(100 + ArgPrecedence(e, mi)); } return(2000); }