protected internal StandardUserDataDescriptor(Type type, InteropAccessMode accessMode, string friendlyName) { if (accessMode == InteropAccessMode.Default) { accessMode = UserData.DefaultAccessMode; } Type = type; Name = type.FullName; AccessMode = accessMode; FriendlyName = friendlyName; if (AccessMode != InteropAccessMode.HideMembers) { foreach (MethodInfo mi in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.Static)) { if (CheckVisibility(mi.GetCustomAttributes(true), mi.IsPublic)) { if (mi.IsSpecialName) { continue; } var md = new StandardUserDataMethodDescriptor(mi, this.AccessMode); if (m_Methods.ContainsKey(md.Name)) { continue; } //throw new ArgumentException(string.Format("{0}.{1} has overloads", Name, md.Name)); m_Methods.Add(md.Name, md); } } foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (CheckVisibility(pi.GetCustomAttributes(true), IsPropertyInfoPublic(pi))) { var pd = new StandardUserDataPropertyDescriptor(pi, this.AccessMode); m_Properties.Add(pd.Name, pd); } } } }
private void Cache(bool hasObject, CallbackArguments args, StandardUserDataMethodDescriptor bestOverload) { int lowestHits = int.MaxValue; OverloadCacheItem found = null; for (int i = 0; i < m_Cache.Length; i++) { if (m_Cache[i] == null) { found = new OverloadCacheItem() { ArgsDataType = new List <DataType>() }; m_Cache[i] = found; break; } else if (m_Cache[i].HitIndexAtLastHit < lowestHits) { lowestHits = m_Cache[i].HitIndexAtLastHit; found = m_Cache[i]; } } if (found == null) { // overflow.. m_Cache = new OverloadCacheItem[CACHE_SIZE]; found = new OverloadCacheItem() { ArgsDataType = new List <DataType>() }; m_Cache[0] = found; m_CacheHits = 0; } found.Method = bestOverload; found.HitIndexAtLastHit = ++m_CacheHits; found.ArgsDataType.Clear(); found.HasObject = hasObject; for (int i = 0; i < args.Count; i++) { found.ArgsDataType.Add(args[i].Type); } }
/// <summary> /// Initializes a new instance of the <see cref="StandardUserDataOverloadedMethodDescriptor" /> class. /// </summary> /// <param name="name">The name.</param> /// <param name="declaringType">The declaring type.</param> /// <param name="descriptor">The descriptor of the first overloaded method.</param> public StandardUserDataOverloadedMethodDescriptor(string name, Type declaringType, StandardUserDataMethodDescriptor descriptor) : this(name, declaringType) { m_Overloads.Add(descriptor); }
/// <summary> /// Calculates the score for the overload. /// </summary> /// <param name="context">The context.</param> /// <param name="args">The arguments.</param> /// <param name="method">The method.</param> /// <param name="isExtMethod">if set to <c>true</c>, is an extension method.</param> /// <returns></returns> private int CalcScoreForOverload(ScriptExecutionContext context, CallbackArguments args, StandardUserDataMethodDescriptor method, bool isExtMethod) { int totalScore = ScriptToClrConversions.WEIGHT_EXACT_MATCH; int argsBase = args.IsMethodCall ? 1 : 0; int argsCnt = argsBase; bool varArgsUsed = false; for (int i = 0; i < method.Parameters.Length; i++) { if (isExtMethod && i == 0) { continue; } Type parameterType = method.Parameters[i].ParameterType; if ((parameterType == typeof(Script)) || (parameterType == typeof(ScriptExecutionContext)) || (parameterType == typeof(CallbackArguments))) { continue; } if (i == method.Parameters.Length - 1 && method.VarArgsArrayType != null) { int varargCnt = 0; DynValue firstArg = null; int scoreBeforeVargars = totalScore; // update score for varargs while (true) { var arg = args.RawGet(argsCnt, false); if (arg == null) { break; } if (firstArg == null) { firstArg = arg; } argsCnt += 1; varargCnt += 1; int score = CalcScoreForSingleArgument(method.VarArgsElementType, arg, isOptional: false); totalScore = Math.Min(totalScore, score); } // check if exact-match if (varargCnt == 1) { if (firstArg.Type == DataType.UserData && firstArg.UserData.Object != null) { if (method.VarArgsArrayType.IsAssignableFrom(firstArg.UserData.Object.GetType())) { totalScore = scoreBeforeVargars; continue; } } } // apply varargs penalty to score if (varargCnt == 0) { totalScore = Math.Min(totalScore, ScriptToClrConversions.WEIGHT_VARARGS_EMPTY); } varArgsUsed = true; } else { var arg = args.RawGet(argsCnt, false) ?? DynValue.Void; int score = CalcScoreForSingleArgument(parameterType, arg, !(method.Parameters[i].DefaultValue.IsDbNull())); totalScore = Math.Min(totalScore, score); argsCnt += 1; } } if (totalScore > 0) { if ((args.Count - argsBase) <= method.Parameters.Length) { totalScore += ScriptToClrConversions.WEIGHT_NO_EXTRA_PARAMS_BONUS; totalScore *= 1000; } else if (varArgsUsed) { totalScore -= ScriptToClrConversions.WEIGHT_VARARGS_MALUS; totalScore *= 1000; } else { totalScore *= 1000; totalScore -= ScriptToClrConversions.WEIGHT_EXTRA_PARAMS_MALUS * ((args.Count - argsBase) - method.Parameters.Length); totalScore = Math.Max(1, totalScore); } } #if DEBUG_OVERLOAD_RESOLVER System.Diagnostics.Debug.WriteLine(string.Format("[OVERLOAD] : Score {0} for method {1}", totalScore, method.SortDiscriminant)); #endif return(totalScore); }
/// <summary> /// Performs the overloaded call. /// </summary> /// <param name="script">The script.</param> /// <param name="obj">The object.</param> /// <param name="context">The context.</param> /// <param name="args">The arguments.</param> /// <returns></returns> /// <exception cref="ScriptRuntimeException">function call doesn't match any overload</exception> private DynValue PerformOverloadedCall(Script script, object obj, ScriptExecutionContext context, CallbackArguments args) { bool extMethodCacheNotExpired = IgnoreExtensionMethods || m_ExtensionMethodVersion == UserData.GetExtensionMethodsChangeVersion(); // common case, let's optimize for it if (m_Overloads.Count == 1 && m_ExtOverloads.Count == 0 && extMethodCacheNotExpired) { return(m_Overloads[0].Callback(script, obj, context, args)); } if (m_Unsorted) { m_Overloads.Sort(); m_Unsorted = false; } if (extMethodCacheNotExpired) { for (int i = 0; i < m_Cache.Length; i++) { if (m_Cache[i] != null && CheckMatch(obj != null, args, m_Cache[i])) { #if DEBUG_OVERLOAD_RESOLVER System.Diagnostics.Debug.WriteLine(string.Format("[OVERLOAD] : CACHED! slot {0}, hits: {1}", i, m_CacheHits)); #endif return(m_Cache[i].Method.Callback(script, obj, context, args)); } } } // resolve on overloads first int maxScore = 0; StandardUserDataMethodDescriptor bestOverload = null; for (int i = 0; i < m_Overloads.Count; i++) { if (obj != null || m_Overloads[i].IsStatic) { int score = CalcScoreForOverload(context, args, m_Overloads[i], false); if (score > maxScore) { maxScore = score; bestOverload = m_Overloads[i]; } } } if (!IgnoreExtensionMethods && (obj != null)) { if (!extMethodCacheNotExpired) { m_ExtensionMethodVersion = UserData.GetExtensionMethodsChangeVersion(); m_ExtOverloads = UserData.GetExtensionMethodsByName(this.Name) .Where(d => d.ExtensionMethodType != null && d.ExtensionMethodType.IsAssignableFrom(this.DeclaringType)) .ToList(); } for (int i = 0; i < m_ExtOverloads.Count; i++) { int score = CalcScoreForOverload(context, args, m_ExtOverloads[i], true); if (score > maxScore) { maxScore = score; bestOverload = m_ExtOverloads[i]; } } } if (bestOverload != null) { Cache(obj != null, args, bestOverload); return(bestOverload.Callback(script, obj, context, args)); } throw new ScriptRuntimeException("function call doesn't match any overload"); }
/// <summary> /// Adds an overload. /// </summary> /// <param name="overload">The overload.</param> public void AddOverload(StandardUserDataMethodDescriptor overload) { m_Overloads.Add(overload); m_Unsorted = true; }
public static DynValue CreateCallbackDynValue(Script script, MethodInfo mi, object obj = null) { var desc = new StandardUserDataMethodDescriptor(mi); return(desc.GetCallbackAsDynValue(script, obj)); }
/// <summary> /// Checks if the event is compatible with a standard descriptor /// </summary> /// <param name="ei">The EventInfo.</param> /// <param name="throwException">if set to <c>true</c> an exception with the proper error message is thrown if not compatible.</param> /// <returns></returns> /// <exception cref="System.ArgumentException"> /// Thrown if throwException is <c>true</c> and one of this applies: /// The event is declared in a value type /// or /// The event does not have both add and remove methods /// or /// The event handler type doesn't implement a public Invoke method /// or /// The event handler has a return type which is not System.Void /// or /// The event handler has more than MAX_ARGS_IN_DELEGATE parameters /// or /// The event handler has a value type parameter or a by ref parameter /// or /// The event handler signature is not a valid method according to <see cref="StandardUserDataMethodDescriptor.CheckMethodIsCompatible"/> /// </exception> public static bool CheckEventIsCompatible(EventInfo ei, bool throwException) { if (ei.DeclaringType.IsValueType) { if (throwException) { throw new ArgumentException("Events are not supported on value types"); } return(false); } if ((ei.GetAddMethod(true) == null) || (ei.GetRemoveMethod(true) == null)) { if (throwException) { throw new ArgumentException("Event must have add and remove methods"); } return(false); } MethodInfo invoke = ei.EventHandlerType.GetMethod("Invoke"); if (invoke == null) { if (throwException) { throw new ArgumentException("Event handler type doesn't seem to be a delegate"); } return(false); } if (!StandardUserDataMethodDescriptor.CheckMethodIsCompatible(invoke, throwException)) { return(false); } if (invoke.ReturnType != typeof(void)) { if (throwException) { throw new ArgumentException("Event handler cannot have a return type"); } return(false); } ParameterInfo[] pars = invoke.GetParameters(); if (pars.Length > MAX_ARGS_IN_DELEGATE) { if (throwException) { throw new ArgumentException(string.Format("Event handler cannot have more than {0} parameters", MAX_ARGS_IN_DELEGATE)); } return(false); } foreach (ParameterInfo pi in pars) { if (pi.ParameterType.IsValueType) { if (throwException) { throw new ArgumentException("Event handler cannot have value type parameters"); } return(false); } else if (pi.ParameterType.IsByRef) { if (throwException) { throw new ArgumentException("Event handler cannot have by-ref type parameters"); } return(false); } } return(true); }
/// <summary> /// Initializes a new instance of the <see cref="StandardUserDataDescriptor"/> class. /// </summary> /// <param name="type">The type this descriptor refers to.</param> /// <param name="accessMode">The interop access mode this descriptor uses for members access</param> /// <param name="friendlyName">A human readable friendly name of the descriptor.</param> protected internal StandardUserDataDescriptor(Type type, InteropAccessMode accessMode, string friendlyName) { if (Script.GlobalOptions.Platform.IsRunningOnAOT()) { accessMode = InteropAccessMode.Reflection; } if (accessMode == InteropAccessMode.Default) { accessMode = UserData.DefaultAccessMode; } Type = type; Name = type.FullName; AccessMode = accessMode; FriendlyName = friendlyName; if (AccessMode != InteropAccessMode.HideMembers) { // get first constructor StandardUserDataOverloadedMethodDescriptor constructors = null; foreach (ConstructorInfo ci in type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { StandardUserDataMethodDescriptor md = StandardUserDataMethodDescriptor.TryCreateIfVisible(ci, this.AccessMode); if (md != null) { if (constructors == null) { constructors = new StandardUserDataOverloadedMethodDescriptor("__new", this.Type) { IgnoreExtensionMethods = true } } ; constructors.AddOverload(md); } } // valuetypes don't reflect their empty ctor.. actually empty ctors are a perversion, we don't care and implement ours if (type.IsValueType) { if (constructors == null) { constructors = new StandardUserDataOverloadedMethodDescriptor("__new", this.Type); } constructors.AddOverload(new StandardUserDataMethodDescriptor(type, accessMode)); } if (constructors != null) { m_Methods.Add("__new", constructors); } // get methods foreach (MethodInfo mi in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { StandardUserDataMethodDescriptor md = StandardUserDataMethodDescriptor.TryCreateIfVisible(mi, this.AccessMode); if (md != null) { if (!StandardUserDataMethodDescriptor.CheckMethodIsCompatible(mi, false)) { continue; } Dictionary <string, StandardUserDataOverloadedMethodDescriptor> dic = m_Methods; string name = mi.Name; if (mi.IsSpecialName && (mi.Name == CASTINGS_EXPLICIT || mi.Name == CASTINGS_IMPLICIT)) { name = GetConversionMethodName(mi.ReturnType); } if (m_Methods.ContainsKey(name)) { m_Methods[name].AddOverload(md); } else { m_Methods.Add(name, new StandardUserDataOverloadedMethodDescriptor(name, this.Type, md)); } foreach (string metaname in GetMetaNamesFromAttributes(mi)) { if (m_MetaMethods.ContainsKey(metaname)) { m_MetaMethods[metaname].AddOverload(md); } else { m_MetaMethods.Add(metaname, new StandardUserDataOverloadedMethodDescriptor(metaname, this.Type, md) { IgnoreExtensionMethods = true }); } } } } // get properties foreach (PropertyInfo pi in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (pi.IsSpecialName || pi.GetIndexParameters().Any()) { continue; } var pd = StandardUserDataPropertyDescriptor.TryCreateIfVisible(pi, this.AccessMode); if (pd != null) { m_Properties.Add(pd.Name, pd); } } // get fields foreach (FieldInfo fi in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (fi.IsSpecialName) { continue; } var fd = StandardUserDataFieldDescriptor.TryCreateIfVisible(fi, this.AccessMode); if (fd != null) { m_Fields.Add(fd.Name, fd); } } // get events foreach (EventInfo ei in type.GetEvents(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (ei.IsSpecialName) { continue; } var ed = StandardUserDataEventDescriptor.TryCreateIfVisible(ei, this.AccessMode); if (ed != null) { m_Events.Add(ei.Name, ed); } } } }