PowerToys/Pythonnet.Runtime/methodbinder.cs
2014-01-11 00:19:14 +08:00

466 lines
17 KiB
C#

// ==========================================================================
// This software is subject to the provisions of the Zope Public License,
// Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
// THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
// WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
// FOR A PARTICULAR PURPOSE.
// ==========================================================================
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 () {
this.list = new ArrayList();
}
internal MethodBinder(MethodInfo mi) : base () {
this.list = new ArrayList();
this.list.Add(mi);
}
public int Count {
get { return this.list.Count; }
}
internal void AddMethod(MethodBase m) {
this.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) {
int count = tp.Length;
for (int i = 0; i < mi.Length; i++) {
ParameterInfo[] pi = mi[i].GetParameters();
if (pi.Length != count) {
continue;
}
for (int n = 0; n < pi.Length; n++) {
if (tp[n]!= pi[n].ParameterType) {
break;
}
if (n == (pi.Length - 1)) {
return mi[i];
}
}
}
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) {
int count = tp.Length;
for (int i = 0; i < mi.Length; i++) {
if (!mi[i].IsGenericMethodDefinition) {
continue;
}
Type[] args = mi[i].GetGenericArguments();
if (args.Length != count) {
continue;
}
return mi[i].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)
{
int genericCount = genericTp.Length;
int signatureCount = sigTp.Length;
for (int i = 0; i < mi.Length; i++)
{
if (!mi[i].IsGenericMethodDefinition)
{
continue;
}
Type[] genericArgs = mi[i].GetGenericArguments();
if (genericArgs.Length != genericCount)
{
continue;
}
ParameterInfo[] pi = mi[i].GetParameters();
if (pi.Length != signatureCount)
{
continue;
}
for (int n = 0; n < pi.Length; n++)
{
if (sigTp[n] != pi[n].ParameterType)
{
break;
}
if (n == (pi.Length - 1))
{
MethodInfo match = mi[i];
if (match.IsGenericMethodDefinition)
{
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 precendence (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 w/this and tweak as necessary.
//====================================================================
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 (int 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);
if (tc == TypeCode.Object) return 1;
if (tc == TypeCode.UInt64) return 10;
if (tc == TypeCode.UInt32) return 11;
if (tc == TypeCode.UInt16) return 12;
if (tc == TypeCode.Int64) return 13;
if (tc == TypeCode.Int32) return 14;
if (tc == TypeCode.Int16) return 15;
if (tc == TypeCode.Char) return 16;
if (tc == TypeCode.SByte) return 17;
if (tc == TypeCode.Byte) return 18;
if (tc == TypeCode.Single) return 20;
if (tc == TypeCode.Double) return 21;
if (tc == TypeCode.String) return 30;
if (tc == 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 this.Bind(inst, args, kw, null, null);
}
internal Binding Bind(IntPtr inst, IntPtr args, IntPtr kw,
MethodBase info) {
return this.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;
int pynargs = Runtime.PyTuple_Size(args);
object arg;
bool isGeneric = false;
if (info != null) {
_methods = (MethodBase[])Array.CreateInstance(
typeof(MethodBase), 1
);
_methods.SetValue(info, 0);
}
else {
_methods = GetMethods();
}
for (int i = 0; i < _methods.Length; i++) {
MethodBase mi = _methods[i];
if (mi.IsGenericMethod) { isGeneric = true; }
ParameterInfo[] pi = mi.GetParameters();
int clrnargs = pi.Length;
bool match = false;
int arrayStart = -1;
int outs = 0;
if (pynargs == clrnargs) {
match = true;
} else if ((pynargs > clrnargs) && (clrnargs > 0) &&
(pi[clrnargs-1].ParameterType.IsArray)) {
// The last argument of the mananged functions seems to
// accept multiple arguments as a array. Hopefully it's a
// spam(params object[] egg) style method
match = true;
arrayStart = clrnargs - 1;
}
if (match) {
Object[] margs = new Object[clrnargs];
for (int n = 0; n < clrnargs; n++) {
IntPtr op;
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);
}
Type type = pi[n].ParameterType;
if (pi[n].IsOut || type.IsByRef) {
outs++;
}
if (!Converter.ToManaged(op, type, out arg, false)) {
Exceptions.Clear();
margs = null;
break;
}
if (arrayStart == n) {
// GetSlice() creates a new reference but GetItem()
// returns only a borrow reference.
Runtime.Decref(op);
}
margs[n] = arg;
}
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'
CLRObject 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 = MethodBinder.MatchParameters(methodinfo, types);
return Bind(inst, args, kw, mi, null);
}
return null;
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw) {
return this.Invoke(inst, args, kw, null, null);
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw,
MethodBase info) {
return this.Invoke(inst, args, kw, info, null);
}
internal virtual IntPtr Invoke(IntPtr inst, IntPtr args, IntPtr kw,
MethodBase info, MethodInfo[] methodinfo) {
Binding binding = this.Bind(inst, args, kw, info, methodinfo);
Object result;
IntPtr ts = IntPtr.Zero;
if (binding == null) {
Exceptions.SetError(Exceptions.TypeError,
"No method matches given arguments"
);
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).
MethodInfo mi = (MethodInfo)binding.info;
if ((binding.outs == 1) && (mi.ReturnType == typeof(void))) {
}
if (binding.outs > 0) {
ParameterInfo[] pi = mi.GetParameters();
int c = pi.Length;
int 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 (int 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.Incref(v);
Runtime.Decref(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) {
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;
}
}
}