Jint and extension methods

Feb 12, 2010 at 7:18 PM

Hi all,

Thanks for your quick reply to my last question!  I have another one.

Can Jint access extension methods?  When I try, I get "Unable to retrieve security descriptor for this frame."  As in,

 

public class Description : Attribute
{
    public string Text;
    
    public Description(string text)
    {
        Text = text;
    }
}

public static class EnumExtensionMethods
{
    public static string Description(this Enum en)
    {
        Description[] da = (Description[])(en.GetType().GetField(en.ToString()).GetCustomAttributes(typeof(Description), false));
        return da != null && da.Length > 0 ? da[0].Text : en.ToString();
    }
}

public enum AnEnum
{
    [Description("Unkown")]
    [SortOrder(0)]
    Unknown = 0
}

Then, I have myEngine.Run("MyObject.MyEnumValue.Description()"), which throws the exception.  The assembly that my engine is running in doesn't reference the assembly that the extension is in.  Any ideas?

 

Thanks,

    Don

Feb 13, 2010 at 12:18 PM

Hi,

 

No Jint cannot access extensions method because the link with extension methods is replaced at compile time. Therefore it is impossible, unless we make some mechanism to register class that provide extension methods

Feb 15, 2010 at 5:47 PM

Okay, thanks!

Feb 16, 2010 at 6:52 PM

I've looked a little bit at the Jint source code to see how hard it would be to add this.  It seems like I would modify CachedMethodInvoker.Invoke to check whatever assemblies I wanted with code similar to http://stackoverflow.com/questions/299515/c-reflection-to-identify-extension-methods.  I'm not sure where I would get the list of assemblies.  Would modifying ExecutionVisitor to include the assembly list work?

Feb 17, 2010 at 2:26 PM

I locally modified CachedMethodInvoker.Invoke by adding an out parameter, out bool isExtensionMethod, set isExtensionMethod to false in the beginning, then added the following code:

if (methodInfo == null)
{

    #region Extension method with exact parameter types

    isExtensionMethod = true;

    foreach (Type t in visitor.ExtensionTypes)
    {
        if (t.IsSealed && !t.IsGenericType && !t.IsNested)
        {
            foreach (MethodInfo m in t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
            {
                ParameterInfo[] pi = m.GetParameters();
                if (pi.GetLength(0) > 0 && pi[0].ParameterType == type)
                {
                    bool isExtensionAttribute = false;
                    foreach (Attribute attr in Attribute.GetCustomAttributes(m, false))
                    {
                        if (attr.GetType().FullName == "System.Runtime.CompilerServices.ExtensionAttribute")
                        {
                            isExtensionAttribute = true;
                            break;
                        }
                    }
                    if (isExtensionAttribute && m.Name == method)
                    {
                        if (m.GetGenericArguments().Length > 0)
                        {
                            // generics can be empty is the type can be inferred from the parameters
                            ms.Add(m.MakeGenericMethod(generics));
                        }
                        else
                        {
                            ms.Add(m);
                        }
                    }
                }
            }
        }
    }

    #region Search exact parameter types

    foreach (var m in ms)
    {
        ParameterInfo[] pis = m.GetParameters();

        bool compatible = true;
        // check types compatibility
        for (int i = 1; i < pis.Length; i++)
        {
            // plymorphic 
            if (parameters[i - 1] != null && pis[i].ParameterType == parameters[i].GetType())
            {
                continue;
            }
            else if (parameters[i - 1] == null && pis[i].ParameterType.IsByRef)
            {
                continue;
            }
            else
            {
                compatible = false;
                break;
            }
        }

        if (!compatible)
        {
            continue;
        }

        methodInfo = m;
        break;
    }

    #endregion

    #endregion
}

I then modified ExecutionVisitor.Visit:

Boolean isExtensionMethod;
MethodInfo methodInfo = methodInvoker.Invoke(callTarget.Value, clrMethod.Value.ToString(), clrParameters, generics.ToArray(), out isExtensionMethod);

if (methodInfo == null)
{
    throw new JintException("Method not found with specified arguments: " + clrMethod.Value.ToString());
}

PermissionSet.PermitOnly();

try
{
    if (! isExtensionMethod)
    {
        result = methodInfo.Invoke(callTarget.Value, clrParameters);
    }
    else
    {
        object[] clrParametersPlusThis = new object[] { callTarget.Value };
        clrParameters.CopyTo(clrParametersPlusThis, 1);

        result = methodInfo.Invoke(null, clrParametersPlusThis);
    }
}
catch (Exception e)
{
    if (e.InnerException is JsException)
    {
        throw e.InnerException;
    }

    throw;
}
Does that seem like a reasonable way to do that?  Is that something you'd find useful and want checked in?

Feb 23, 2010 at 11:10 AM

Hi,

Very interesting, Maybe I would change one or two things, but, the logic is here and looks great. Thanks for your help.