X Tutup
using System; using System.Collections; using System.Reflection; namespace Python.Runtime { /// /// A MethodBinder encapsulates information about a (possibly overloaded) /// managed method, and is responsible for selecting the right method given /// a set of Python arguments. This is also used as a base class for the /// ConstructorBinder, a minor variation used to invoke constructors. /// internal class MethodBinder { public ArrayList list; public MethodBase[] methods; public bool init = false; public bool allow_threads = true; internal MethodBinder() { list = new ArrayList(); } internal MethodBinder(MethodInfo mi) { list = new ArrayList { mi }; } public int Count { get { return list.Count; } } internal void AddMethod(MethodBase m) { list.Add(m); } /// /// Given a sequence of MethodInfo and a sequence of types, return the /// MethodInfo that matches the signature represented by those types. /// internal static MethodInfo MatchSignature(MethodInfo[] mi, Type[] tp) { if (tp == null) { return null; } int count = tp.Length; foreach (MethodInfo t in mi) { ParameterInfo[] pi = t.GetParameters(); if (pi.Length != count) { continue; } for (var n = 0; n < pi.Length; n++) { if (tp[n] != pi[n].ParameterType) { break; } if (n == pi.Length - 1) { return t; } } } return null; } /// /// Given a sequence of MethodInfo and a sequence of type parameters, /// return the MethodInfo that represents the matching closed generic. /// internal static MethodInfo MatchParameters(MethodInfo[] mi, Type[] tp) { if (tp == null) { return null; } int count = tp.Length; foreach (MethodInfo t in mi) { if (!t.IsGenericMethodDefinition) { continue; } Type[] args = t.GetGenericArguments(); if (args.Length != count) { continue; } return t.MakeGenericMethod(tp); } return null; } /// /// Given a sequence of MethodInfo and two sequences of type parameters, /// return the MethodInfo that matches the signature and the closed generic. /// internal static MethodInfo MatchSignatureAndParameters(MethodInfo[] mi, Type[] genericTp, Type[] sigTp) { if (genericTp == null || sigTp == null) { return null; } int genericCount = genericTp.Length; int signatureCount = sigTp.Length; foreach (MethodInfo t in mi) { if (!t.IsGenericMethodDefinition) { continue; } Type[] genericArgs = t.GetGenericArguments(); if (genericArgs.Length != genericCount) { continue; } ParameterInfo[] pi = t.GetParameters(); if (pi.Length != signatureCount) { continue; } for (var n = 0; n < pi.Length; n++) { if (sigTp[n] != pi[n].ParameterType) { break; } if (n == pi.Length - 1) { MethodInfo match = t; if (match.IsGenericMethodDefinition) { // FIXME: typeArgs not used Type[] typeArgs = match.GetGenericArguments(); return match.MakeGenericMethod(genericTp); } return match; } } } return null; } /// /// Return the array of MethodInfo for this method. The result array /// is arranged in order of precedence (done lazily to avoid doing it /// at all for methods that are never called). /// internal MethodBase[] GetMethods() { if (!init) { // I'm sure this could be made more efficient. list.Sort(new MethodSorter()); methods = (MethodBase[])list.ToArray(typeof(MethodBase)); init = true; } return methods; } /// /// Precedence algorithm largely lifted from Jython - the concerns are /// generally the same so we'll start with this and tweak as necessary. /// /// /// Based from Jython `org.python.core.ReflectedArgs.precedence` /// See: https://github.com/jythontools/jython/blob/master/src/org/python/core/ReflectedArgs.java#L192 /// internal static int GetPrecedence(MethodBase mi) { ParameterInfo[] pi = mi.GetParameters(); int val = mi.IsStatic ? 3000 : 0; int num = pi.Length; val += mi.IsGenericMethod ? 1 : 0; for (var i = 0; i < num; i++) { val += ArgPrecedence(pi[i].ParameterType); } return val; } /// /// Return a precedence value for a particular Type object. /// internal static int ArgPrecedence(Type t) { Type objectType = typeof(object); if (t == objectType) { return 3000; } 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); } return 2000; } /// /// Bind the given Python instance and arguments to a particular method /// overload and return a structure that contains the converted Python /// instance, converted arguments and the correct method to call. /// internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw) { return Bind(inst, args, kw, null, null); } internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info) { return Bind(inst, args, kw, info, null); } 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 pynargs = (int)Runtime.PyTuple_Size(args); object arg; var isGeneric = false; ArrayList defaultArgList = null; if (info != null) { _methods = new MethodBase[1]; _methods.SetValue(info, 0); } else { _methods = GetMethods(); } Type clrtype; // TODO: Clean up foreach (MethodBase mi in _methods) { if (mi.IsGenericMethod) { isGeneric = true; } ParameterInfo[] pi = mi.GetParameters(); var clrnargs = pi.Length; var match = false; var arrayStart = -1; var outs = 0; if (pynargs == clrnargs) { match = true; } else if (pynargs < clrnargs) { match = true; defaultArgList = new ArrayList(); for (var v = pynargs; v < clrnargs; v++) { if (pi[v].DefaultValue == DBNull.Value) { match = false; } else { defaultArgList.Add(pi[v].DefaultValue); } } } else if (pynargs > clrnargs && clrnargs > 0 && Attribute.IsDefined(pi[clrnargs - 1], typeof(ParamArrayAttribute))) { // This is a `foo(params object[] bar)` style method match = true; arrayStart = clrnargs - 1; } if (match) { var margs = new object[clrnargs]; for (int n = 0; n < clrnargs; n++) { IntPtr op; if (n < pynargs) { if (arrayStart == n) { // map remaining Python arguments to a tuple since // the managed function accepts it - hopefully :] op = Runtime.PyTuple_GetSlice(args, arrayStart, pynargs); } else { op = Runtime.PyTuple_GetItem(args, n); } // this logic below handles cases when multiple overloading methods // are ambiguous, hence comparison between Python and CLR types // is necessary clrtype = null; IntPtr pyoptype; if (_methods.Length > 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 ((pi[n].ParameterType != typeof(object)) && (pi[n].ParameterType != clrtype)) { IntPtr pytype = Converter.GetPythonTypeByAlias(pi[n].ParameterType); pyoptype = Runtime.PyObject_Type(op); Exceptions.Clear(); if (pyoptype != IntPtr.Zero) { if (pytype != pyoptype) { typematch = false; } else { typematch = true; clrtype = pi[n].ParameterType; } } if (!typematch) { // this takes care of enum values TypeCode argtypecode = Type.GetTypeCode(pi[n].ParameterType); TypeCode paramtypecode = Type.GetTypeCode(clrtype); if (argtypecode == paramtypecode) { typematch = true; clrtype = pi[n].ParameterType; } } Runtime.XDecref(pyoptype); if (!typematch) { margs = null; break; } } else { typematch = true; clrtype = pi[n].ParameterType; } } else { clrtype = pi[n].ParameterType; } if (pi[n].IsOut || clrtype.IsByRef) { outs++; } if (!Converter.ToManaged(op, clrtype, out arg, false)) { Exceptions.Clear(); margs = null; break; } if (arrayStart == n) { // GetSlice() creates a new reference but GetItem() // returns only a borrow reference. Runtime.XDecref(op); } margs[n] = arg; } else { if (defaultArgList != null) { margs[n] = defaultArgList[n - pynargs]; } } } if (margs == null) { 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; } 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 virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw) { return Invoke(inst, args, kw, null, null); } internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info) { return Invoke(inst, args, kw, info, null); } internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw, MethodBase info, MethodInfo[] methodinfo) { Binding binding = Bind(inst, args, kw, info, methodinfo); object result; IntPtr ts = IntPtr.Zero; if (binding == null) { var value = "No method matches given arguments"; if (methodinfo != null && methodinfo.Length > 0) { value += $" for {methodinfo[0].Name}"; } Exceptions.SetError(Exceptions.TypeError, value); return IntPtr.Zero; } if (allow_threads) { ts = PythonEngine.BeginAllowThreads(); } try { result = binding.info.Invoke(binding.inst, BindingFlags.Default, null, binding.args, null); } catch (Exception e) { if (e.InnerException != null) { e = e.InnerException; } if (allow_threads) { PythonEngine.EndAllowThreads(ts); } Exceptions.SetError(e); return IntPtr.Zero; } if (allow_threads) { PythonEngine.EndAllowThreads(ts); } // If there are out parameters, we return a tuple containing // the result followed by the out parameters. If there is only // one out parameter and the return type of the method is void, // we return the out parameter as the result to Python (for // code compatibility with ironpython). var mi = (MethodInfo)binding.info; if (binding.outs == 1 && mi.ReturnType == typeof(void)) { } if (binding.outs > 0) { ParameterInfo[] pi = mi.GetParameters(); int c = pi.Length; var n = 0; IntPtr t = Runtime.PyTuple_New(binding.outs + 1); IntPtr v = Converter.ToPython(result, mi.ReturnType); Runtime.PyTuple_SetItem(t, n, v); n++; for (var i = 0; i < c; i++) { Type pt = pi[i].ParameterType; if (pi[i].IsOut || pt.IsByRef) { v = Converter.ToPython(binding.args[i], pt); Runtime.PyTuple_SetItem(t, n, v); n++; } } if (binding.outs == 1 && mi.ReturnType == typeof(void)) { v = Runtime.PyTuple_GetItem(t, 1); Runtime.XIncref(v); Runtime.XDecref(t); return v; } return t; } return Converter.ToPython(result, mi.ReturnType); } } /// /// Utility class to sort method info by parameter type precedence. /// internal class MethodSorter : IComparer { int IComparer.Compare(object m1, object m2) { var me1 = (MethodBase)m1; var me2 = (MethodBase)m2; if (me1.DeclaringType != me2.DeclaringType) { // m2's type derives from m1's type, favor m2 if (me1.DeclaringType.IsAssignableFrom(me2.DeclaringType)) return 1; // m1's type derives from m2's type, favor m1 if (me2.DeclaringType.IsAssignableFrom(me1.DeclaringType)) return -1; } int p1 = MethodBinder.GetPrecedence((MethodBase)m1); int p2 = MethodBinder.GetPrecedence((MethodBase)m2); if (p1 < p2) { return -1; } if (p1 > p2) { return 1; } return 0; } } /// /// A Binding is a utility instance that bundles together a MethodInfo /// representing a method to call, a (possibly null) target instance for /// the call, and the arguments for the call (all as managed values). /// internal class Binding { public MethodBase info; public object[] args; public object inst; public int outs; internal Binding(MethodBase info, object inst, object[] args, int outs) { this.info = info; this.inst = inst; this.args = args; this.outs = outs; } } }
X Tutup