Invokebinder stopped ByRef parameter passing

Sep 17, 2009 at 7:21 AM

I have a case that I got the correct ByRef behavior when invoking the function directly but the ByRef behavior is lost when I invoke it through a invoke binder. Sorry that the code is a bit long, but let me explain here:

I have a function generated by getMyFunc which has a byref parameter.  In lamda1, I use Expression.Invoke and I got the correct result 3. In lamda2, I used a invokebinder modified from the Smpl sample. I carefully modified ConvertArguments() so that the argument is actually not modified by a Expression.Convert(). However, I got the result of 2 which is a ByVal behavior. I wonder where the ByRef behavior is stopped. Thanks.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Scripting;
using Microsoft.Scripting.Ast;
using System.Dynamic;
using System.Reflection;
using Debug = System.Diagnostics.Debug;

namespace DynamicTest
{
    class Program
    {
        static void Main(string[] args)
        {
            ParameterExpression myVar = Expression.Variable(typeof(object));
            LambdaExpression lamda1 = Expression.Lambda(
                Expression.Block(
                    new ParameterExpression[] { myVar },
                    Expression.Assign(
                        myVar,
                        Expression.Constant(2, typeof(object))
                    ),
                    Expression.Invoke(getMyFunc(), myVar),
                    myVar
                )
            );


            Console.WriteLine(lamda1.Compile().DynamicInvoke()); //Expect 3

            LambdaExpression lamda2 = Expression.Lambda(
                Expression.Block(
                    new ParameterExpression[] { myVar },
                    Expression.Assign(
                        myVar,
                        Expression.Constant(2, typeof(object))
                    ),
                    Expression.Dynamic(
                        new MyInvokeBinder(new CallInfo(1)),
                        typeof(object),
                        getMyFunc(),
                        myVar
                    ),
                    myVar
                )
            );
            Console.WriteLine(lamda2.Compile().DynamicInvoke()); //Expect 3

            Console.ReadLine();

        }

        static Expression getMyFunc()
        {
            ParameterExpression myParam = Expression.Parameter(typeof(object).MakeByRefType());
            return Expression.Lambda(
                Expression.Assign(
                    myParam,
                    Expression.Convert(
                        Expression.Add(
                            Expression.Convert(myParam, typeof(int)),
                            Expression.Constant(1)
                        ),
                        typeof(object)
                    )
                ),
                myParam
            );
        }
    }

    public class MyInvokeBinder : InvokeBinder
    {
        public MyInvokeBinder(CallInfo callinfo)
            : base(callinfo)
        {
        }

        public override DynamicMetaObject FallbackInvoke(
                DynamicMetaObject targetMO, DynamicMetaObject[] argMOs,
                DynamicMetaObject errorSuggestion)
        {
            if (targetMO.LimitType.IsSubclassOf(typeof(Delegate)))
            {
                var parms = targetMO.LimitType.GetMethod("Invoke").GetParameters();
                if (parms.Length == argMOs.Length)
                {
                    var callArgs = ConvertArguments(argMOs, parms);
                    var expression = Expression.Invoke(
                        Expression.Convert(targetMO.Expression, targetMO.LimitType),
                        callArgs);
                    return new DynamicMetaObject(
                        EnsureObjectResult(expression),
                        BindingRestrictions.GetTypeRestriction(targetMO.Expression,
                                                               targetMO.LimitType));
                }
            }
            return errorSuggestion ??
                CreateThrow(
                    targetMO, argMOs,
                    BindingRestrictions.GetTypeRestriction(targetMO.Expression,
                                                           targetMO.LimitType),
                    typeof(InvalidOperationException),
                    "Wrong number of arguments for function -- " +
                    targetMO.LimitType.ToString() + " got " + argMOs.ToString());

        }

        public static Expression[] ConvertArguments(
                         DynamicMetaObject[] args, ParameterInfo[] ps)
        {
            Debug.Assert(args.Length == ps.Length,
                         "Internal: args are not same len as params?!");
            Expression[] callArgs = new Expression[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                Expression argExpr = args[i].Expression;
                Type paramType;
                if (ps[i].ParameterType.IsByRef)
                {
                    paramType = ps[i].ParameterType.GetElementType();
                }
                else
                {
                    paramType = ps[i].ParameterType;
                }
                if (argExpr.Type != paramType)
                {
                    argExpr = Expression.Convert(argExpr, paramType);
                }
                callArgs[i] = argExpr;
            }
            return callArgs;
        }

        public static DynamicMetaObject CreateThrow
        (DynamicMetaObject target, DynamicMetaObject[] args,
         BindingRestrictions moreTests,
         Type exception, params object[] exceptionArgs)
        {
            Expression[] argExprs = null;
            Type[] argTypes = Type.EmptyTypes;
            int i;
            if (exceptionArgs != null)
            {
                i = exceptionArgs.Length;
                argExprs = new Expression[i];
                argTypes = new Type[i];
                i = 0;
                foreach (object o in exceptionArgs)
                {
                    Expression e = Expression.Constant(o);
                    argExprs[i] = e;
                    argTypes[i] = e.Type;
                    i += 1;
                }
            }
            ConstructorInfo constructor = exception.GetConstructor(argTypes);
            if (constructor == null)
            {
                throw new ArgumentException(
                    "Type doesn't have constructor with a given signature");
            }
            return new DynamicMetaObject(
                Expression.Throw(
                    Expression.New(constructor, argExprs),
                // Force expression to be type object so that DLR CallSite
                // code things only type object flows out of the CallSite.
                    typeof(object)),
                target.Restrictions.Merge(BindingRestrictions.Combine(args))
                                   .Merge(moreTests));
        }

        public static Expression EnsureObjectResult(Expression expr)
        {
            if (!expr.Type.IsValueType)
                return expr;
            if (expr.Type == typeof(void))
                return Expression.Block(
                           expr, Expression.Default(typeof(object)));
            else
                return Expression.Convert(expr, typeof(object));
        }
    }

}

Sep 17, 2009 at 8:14 PM

I found the answer to my own question. What happened is (I think) that the callsite generated a delegate that in turns call the target and the delegate does not support byref. In order to support byref, I cannot use Expression.Dynamic. Instead, I have to use Expression.MakeDynamic with which I can explicitly specify the signature of the callsite delegate.

This is what I did to make it work:

1) Create a delegate with the right signature at the beginning of the class:

<font size="2" color="#0000ff"><font size="2" color="#0000ff">

delegate

</font></font><font size="2" color="#0000ff">

 

</font>

object dummy(CallSite c, ref object i1, ref object i2);

2) Replace the Expression.Dynamic() with Expression.MakeDynamic():

 Expression.MakeDynamic(
                        typeof(dummy),
                        new MyInvokeBinder(new CallInfo(1)),
                        getMyFunc(),
                        myVar
                    )