Invoking dynamic and static delegates/lambdas with DynamicInvoke

Jan 5, 2010 at 8:24 AM
Edited Jan 5, 2010 at 8:28 AM

I've started implementing my own test/toy language, which is aching towards JavaScript. It all works, but I wanted to ask if I'm going about this the right way. I have a class NativeObj that implements IDynamicMetaObjectProvider that returns an instance of NativeObjMeta which extends the built in DynamicMetaObject. The NativeObj object has a field named Callee which has the type Delegate, it holds a delegate of unknown type which is assigned from C# (I havn't  implemented the Expression Tree generation for Lambdas yet, but I'm assuming I can assign them to the Callee field in a NativeObj also?).

The point is that I wan't to be able to invoke NativeObj from my own languages code, with something like: var foo = bar(); if bar was an object of type NativeObj, here's what I did: I overloaded the BindInvoke method on NativeObjMeta and scribbled down a few lines of code that do the following steps:

  1. Creates a MakeMemberAccess expression that accesses the Callee field of the NativeObj
  2. Collects the Expression objects of the args parameter into an array
  3. Creates a MethodInfo object for the DynamicInvoke method from Delegate
  4. Emits a Call expression that calls the DynamicInvoke method on the field accessed by the MakeMemberAccess expr with the Expression arguments extracted from args

The code looks like this (all bounds checking, debugging, safety, etc. has been stripped out for brevity):

                    var callTarget =
                        Expr.MakeMemberAccess(
                            Helper.CastToNativeObj(this.Expression),
                            typeof(NativeObj).GetMember("Callee")[0]
                        );

                    var callArgs = args.Select(x => x.Expression).ToArray();
                    var methodInfo = typeof(Delegate).GetMethod("DynamicInvoke");
                    var callExpr = Expr.Call(callTarget, methodInfo, Expr.NewArrayInit(typeof(object), callArgs));

                    return new DynamicMetaObject(
                        callExpr,
                        this.Restrictions.Merge(
                            Helper.GetTargetArgsRestrictions(this, args, false)
                        )
                    );

What I want to achieve is the ability to assign delegates (of any type) from both C# and my own language through LambdaExpression to the Callee field of a NativeObj object and then call that delegate during runtime.

My question is this: Is this the correct (or at least a good) way to go about it to implement the ability to call unknown (unknown here means delegates/lambdas casted to the Delegate type since I have no way of knowing their real type at runtime) delegates/lambdas (created both in C# and from an AST)? I've been going through the IronPython source code to try to figure out how it does things, but to no avail.

Coordinator
Jan 5, 2010 at 5:46 PM

You’re close but you’ve taken one wrong turn.  Rather than invoke DynamicInvoke you really want to invoke Invoke on the specific delegate type that you’re calling.  This will give you much better performance.  You’ll also need to do any necessary conversions for calling Invoke – usually with dynamic languages that’s just boxing – IronPython uses AstUtils.SimpleCallHelper to do this.  Finally if you’re not you’ll want to add a restriction which restricts not only to NativeObj but also NativeObj.Callee to the type of delegate.    So I’d change your code to something like:

                    var callTarget =
                        Expr.MakeMemberAccess(
                            Helper.CastToNativeObj(this.Expression),
                            typeof(NativeObj).GetMember("Callee")[0]
                        );
 
                    var callArgs = args.Select(x => x.Expression).ToArray();
                    var methodInfo = ((NativeObj)this.Value).Calle.GetType().GetMethod("DynamicInvoke");
                    var callExpr = AstUtils.SimpleCallHelper(Expr.Convert(callTarget, ((NativeObj)this.Value).Calle.GetType()), methodInfo, Expr.NewArrayInit(typeof(object), callArgs));
 
                    return new DynamicMetaObject(
                        callExpr,
                        this.Restrictions.Merge(
                            Helper.GetTargetArgsRestrictions(this, args, false)     // also this needs to add the restriction if it’s not already there
                        )
                    );

Also if you want a pointer into IronPython where this is happening it’s MetaPythonFunction.FunctionBinderHelper.MakeFunctionInvoke – but IronPython is dealing with a lot more things like splatting of arguments.

From: fholm [mailto:notifications@codeplex.com]
Sent: Tuesday, January 05, 2010 1:25 AM
To: Dino Viehland
Subject: Invoking dynamic and static delegates/lambdas with DynamicInvoke [dlr:79771]

From: fholm

I've started implementing my own test/toy language, which is aching towards JavaScript. It all works, but I wanted to ask if I'm going about this the right way. I have a class NativeObj that implements IDynamicMetaObjectProvider that returns an instance of NativeObjMeta which extends the built in DynamicMetaObject. The NativeObj object has a field named Callee which has the type Delegate, it holds a delegate of unknown type which is assigned from C# (I havn't implemented the Expression Tree generation for Lambdas yet, but I'm assuming I can assign them to the Callee field in a NativeObj also?).

The point is that I wan't to be able to invoke NativeObj from my own languages code, with something like: var foo = bar(); if bar was an object of type NativeObj, here's what I did: I overloaded the BindInvoke method on NativeObjMeta and scribbled down a few lines of code that do the following steps:

1. Creates a MakeMemberAccess expression that accesses the Callee field of the NativeObj

2. Collects the Expression objects of the args parameter into an array

3. Creates a MethodInfo object for the DynamicInvoke method from Delegate

4. Emits a Call expression that calls the DynamicInvoke method on the field accessed by the MakeMemberAccess expr with the Expression arguments extracted from args

The code looks like this (all bounds checking, debugging, safety, etc. has been stripped out for brevity):

                    var callTarget =
                        Expr.MakeMemberAccess(
                            Helper.CastToNativeObj(this.Expression),
                            typeof(NativeObj).GetMember("Callee")[0]
                        );
 
                    var callArgs = args.Select(x => x.Expression).ToArray();
                    var methodInfo = typeof(Delegate).GetMethod("DynamicInvoke");
                    var callExpr = Expr.Call(callTarget, methodInfo, Expr.NewArrayInit(typeof(object), callArgs));
 
                    return new DynamicMetaObject(
                        callExpr,
                        this.Restrictions.Merge(
                            Helper.GetTargetArgsRestrictions(this, args, false)
                        )
                    );

What I want to achieve is the ability to assign delegates (of any type) to the Callee field of a NativeObj object and then call that delegate during runtime.

My question is this: Is this the correct (or at least a good) way to go about it to implement the ability to call unknown (unknown here means delegates/lambdas casted to the Delegate type since I have no way of knowing their real type at runtime) delegates/lambdas (created both in C# and from an AST)? I've been going through the IronPython source code to try to figure out how it does things, but to no avail.

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Jan 5, 2010 at 6:24 PM

dinov: thanks a lot! That was exactly what I was after, I've been messing around all evening with this and the code I got to was close to what you said, but not exactly.

One thing however, you said that I needed to add restrictions on NativeObj.Callee, would that be built using BindingRestrictions.GetExpressionRestriction? I can't see how that would be possible to do with .GetInstanceRestriction or .GetTypeRestriction? How would that look? Is that an equality comparison with the callTarget ?

Also the code formatting looks to have been a bit messed up, but I'm assuming you meant something like this: 

 

   var callArgs = args.Select(x => x.Expression).ToArray();
   var methodInfo = ((NativeObj)this.Value).Callee.GetType().GetMethod("Invoke");
   var callExpr = AstUtils.SimpleCallHelper(callTarget, methodInfo, callArgs);

?

 

Coordinator
Jan 5, 2010 at 6:32 PM

You can use GetTypeRestriction, it just needs to be for the field expression on the NativeObj expression, something like:

BindingRestrictions.GetTypeRestriction(

    Expression.Field(

        Expression.Convert(this.Expression, typeof(NativeObj)),

        “Callee”

    ),

    ((NativeObj)this).Value.Callee.GetType()

);

From: fholm [mailto:notifications@codeplex.com]
Sent: Tuesday, January 05, 2010 11:24 AM
To: Dino Viehland
Subject: Re: Invoking dynamic and static delegates/lambdas with DynamicInvoke [dlr:79771]

From: fholm

dinov: thanks a lot! That was exactly what I was after, I've been messing around all evening with this and the code I got to was close to what you said, but not exactly.

One thing however, you said that I needed to add restrictions on NativeObj.Callee, would that be built using BindingRestrictions.GetExpressionRestriction? I can't see how that would be possible to do with .GetInstanceRestriction or .GetTypeRestriction? How would that look? Is that an equality comparison with the callTarget ?

Also the code formatting looks to have been a bit messed up, but I'm assuming you meant something like this:

   var callArgs = args.Select(x => x.Expression).ToArray();
   var methodInfo = ((NativeObj)this.Value).Callee.GetType().GetMethod("Invoke");
   var callExpr = AstUtils.SimpleCallHelper(callTarget, methodInfo, callArgs);

?

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com

Coordinator
Jan 5, 2010 at 6:33 PM

Oh, and yes on the code formatting – I was just trying to include some context which included some strike through text which might not of come through.

From: fholm [mailto:notifications@codeplex.com]
Sent: Tuesday, January 05, 2010 11:24 AM
To: Dino Viehland
Subject: Re: Invoking dynamic and static delegates/lambdas with DynamicInvoke [dlr:79771]

From: fholm

dinov: thanks a lot! That was exactly what I was after, I've been messing around all evening with this and the code I got to was close to what you said, but not exactly.

One thing however, you said that I needed to add restrictions on NativeObj.Callee, would that be built using BindingRestrictions.GetExpressionRestriction? I can't see how that would be possible to do with .GetInstanceRestriction or .GetTypeRestriction? How would that look? Is that an equality comparison with the callTarget ?

Also the code formatting looks to have been a bit messed up, but I'm assuming you meant something like this:

   var callArgs = args.Select(x => x.Expression).ToArray();
   var methodInfo = ((NativeObj)this.Value).Callee.GetType().GetMethod("Invoke");
   var callExpr = AstUtils.SimpleCallHelper(callTarget, methodInfo, callArgs);

?

Read the full discussion online.

To add a post to this discussion, reply to this email (dlr@discussions.codeplex.com)

To start a new discussion for this project, email dlr@discussions.codeplex.com

You are receiving this email because you subscribed to this discussion on CodePlex. You can unsubscribe or change your settings on codePlex.com.

Please note: Images and attachments will be removed from emails. Any posts to this discussion will also be available online at codeplex.com