Skip to content

symplinvokememberbinder and binding .net member invocations

Stéphane Lozier edited this page Jan 18, 2021 · 1 revision

14 SymplInvokeMemberBinder and Binding .NET Member Invocations

In Sympl member invocations look like this:

some-expr.property.(method "arg").property

(x.tostring)

(x.select (lambda (e) e.Name))

(expr.property.(method).method arg1 arg2) ;; two InvokeMembers

At runtime, when trying to invoke a member of a .NET static object, the default .NET meta-object will call FallbackInvokMember on SymplInvokeMemberBinder. This code is nearly the same as TypeModelMetaObject's BindInvokeMember, with a couple of changes. If the object that flows into the CallSite is a dynamic object, then its meta-object's BindInvokeMember will produce a rule for invoking the member, as TypeModel's meta-object in Hello World.

InvokeMemberBinders need FallbackInvokeMember and FallbackInvoke methods. The second came up in Sympl when cross-library function invocation started working (see section ) due to ExpandObject's implementation of InvokeMember. Some languages and dynamic objects do not or cannot perform InvokeMember operations. They can turn InvokeMember into a GetMember and a call to the InvokeMemberBinder's FallbackInvoke method. IronPython does this, passing a DynamicMetaObject that results in a callable object that is closed over the InvokeMember operation's target object.

14.1 FallbackInvokeMember

This code is nearly the same as TypeModelMetaObject's BindInvokeMember, with a couple of changes, such as using CreateThrow rather than falling back to a binder. The details are discussed below the code for SymplInvokeMemberBinder's FallbackInvokeMember from runtime.cs:

public override DynamicMetaObject FallbackInvokeMember(

DynamicMetaObject targetMO, DynamicMetaObject[] args,

DynamicMetaObject errorSuggestion) {

// ... Deleted checking for COM and need to Defer for now ...

var flags = BindingFlags.IgnoreCase | BindingFlags.Instance |

BindingFlags.Public;

var members = targetMO.LimitType.GetMember(this.Name, flags);

if ((members.Length == 1) && (members[0] is PropertyInfo ||

members[0] is FieldInfo)){

// Code deleted, not implemented yet.

} else {

// Get MethodInfos with right arg counts.

var mi_mems = members.

Select(m => m as MethodInfo).

Where(m => m is MethodInfo &&

((MethodInfo)m).GetParameters().Length ==

args.Length);

List<MethodInfo> res = new List<MethodInfo>();

foreach (var mem in mi_mems) {

if (RuntimeHelpers.ParametersMatchArguments(

mem.GetParameters(), args)) {

res.Add(mem);

}

}

var restrictions = RuntimeHelpers.GetTargetArgsRestrictions(

targetMO, args, false);

if (res.Count == 0) {

return errorSuggestion ??

RuntimeHelpers.CreateThrow(

targetMO, args, restrictions,

typeof(MissingMemberException),

"Can't bind member invoke -- " +

args.ToString());

}

var callArgs = RuntimeHelpers.ConvertArguments(

args,

res[0].GetParameters());

return new DynamicMetaObject(

RuntimeHelpers.EnsureObjectResult(

Expression.Call(

Expression.Convert(targetMO.Expression,

targetMO.LimitType),

res[0], callArgs)),

restrictions);

Let's first talk about what we aren't talking about now. This code snippet omits the code to check if the target is a COM object and to use built-in COM support. See section for information adding this to your binders. The snippet also omits some very important code that protects binders and DynamicMetaObjects from infinitely looping due to producing bad rules. It is best to discuss this in one place, so see section for how the infinite loop happens and how to prevent it for all binders.

Sympl takes the name from the binder's metadata and looks for all public, instance members on the LimitType of the value represented by this meta-object. You could just as easily decide to bind to static members here as well if your language had those semantics. Because Sympl is a case-INsensitive language, the flags include IgnoreCase.

You could also bind to data members that held sub types of Delegate. You'd then emit code to fetch the member, similar to the expression in GetRuntimeTypeMoFromModel, and use an Invoke DynamicExpression. This nests a CallSite and defers binding to SymplInvokeBinder's FallbackInvoke method, similar to what SymplInvokeMemberBinder's FallbackInvoke does. Sympl doesn't bind to data members with delegate values just to simplify the sample.

FallbackInvokeMember filters for only the members that are MethodInfos and have the right number of arguments. Then the binding logic filters for the MethodInfos that have parameters that can be bound given the kinds of arguments present at this invocation of the call site. See section for a discussion of matching parameters in the filtered MethodInfos and choosing the overload to invoke because it is the same logic here.

If FallbackInvokeMember finds no matching MethodInfos, then it either uses the suggested result or creates a DynamicMetaObject result that throws an Exception. See section for a discussion of CreateThrow and restrictions. ErrorSuggestion is discussed in section .

The rest of this function is almost exactly TypeModelMetaObject's BindInvokeMember. See section for a discussion of restrictions and argument conversions for the resulting DynamicMetaObject's MethodCallExpression. See section 3.2.4 for a discussion of EnsureObjectResult. One difference to point out is that FallbackInvokeMember needs to convert the target object to the specific LimitType of the DynamicMetaObject. See section for a discussion of using LimitType over RuntimeType. It may seem odd to convert the object to the type that LimitType reports it to be, but the type of the meta-object's expression might be more general and require an explicit Convert node to satisfy the strict typing of the Expression Tree factory or the actual emitted code that executes. The Expression Tree compiler removes unnecessary Convert nodes.

14.2 FallbackInvoke

This method exists for languages and dynamic objects do not or cannot perform InvokeMember operations. Instead, they can turn InvokeMember into a GetMember and a call to the InvokeMemberBinder's FallbackInvoke method. The DLR's ExpandoObjects do this, which section discusses to get cross-module top-level function calls working. IronPython uses FallbackInvoke, passing a DynamicMetaObject that results in a callable object that is closed over the InvokeMember operation's target object.

Here's the code for SymplInvokeMemberBinder's FallbackInvoke from runtime.cs:

public override DynamicMetaObject FallbackInvoke(

DynamicMetaObject targetMO, DynamicMetaObject[] args,

DynamicMetaObject errorSuggestion) {

var argexprs = new Expression[args.Length + 1];

for (int i = 0; i < args.Length; i++) {

argexprs[i + 1] = args[i].Expression;

}

argexprs[0] = targetMO.Expression;

return new DynamicMetaObject(

Expression.Dynamic(

new SymplInvokeBinder(

new CallInfo(args.Length)),

typeof(object),

argexprs),

targetMO.Restrictions.Merge(

BindingRestrictions.Combine(args)));

The target meta-object passed to FallbackInvoke is a callable object, not the target object passed to FallbackInvokeMember that might have a member with the name in the binder's metadata. There are no checks here for COM objects because no callable COM object should flow into FallbackInvoke.

or whether FallbackInvoke needs to Defer to a nested CallSite. No , and FallbackInvoke effectively always defers to a nested CallSite (section ).

FallbackInvoke just bundles the target and args into an array to pass to the Dynamic factory method. By returning a DynamicMetaObject with a DynamicExpression, FallbackInvoke is creating a nested CallSite, so regardless of whether any argument meta-objects need to defer computation, this code works (see section for information on Defer). As with all CallInfos, the count of arguments does not include the target object even though it is in the arguments array passed to Dynamic.

The restrictions are simple too, but it is important to collect them and propagate them to the new DynamicMetaObject. There's no need to add other restrictions since no other argument conditions were used to compute a binding. This method is just deferring to the SymplInvokeBinder's FallbackInvoke method to figure out a binding.

Note, Sympl calls the SymplInvokeBinder constructor here rather than calling GetInvokeBinder from an instance of the Sympl runtime class (see section ). This means the CallSite resulting from the DynamicExpression will not share any L2 caching with other call sites. At this point in the execution of a Sympl program, Sympl binders do not have access to the Sympl instance on whose behalf the Sympl code is running. Sympl could have added a property to the binder to stash the Sympl runtime instance when creating the InvokeMember DynamicExpression in AnalyzeFunCallExpr, but you want to do that very carefully to make sure you don't hold onto working set unintentionally. Sympl could have used GetInvokeBinder in AnalyzeFunCallExpr and tucked one into the SymplInvokeMemberBinder instance in case it was needed. There are various ways to handle this, but for the sample, losing L2 cache sharing here is acceptable.

SymPL Implementation on the Dynamic Language Runtime

Frontmatter
1 Introduction
  1.1 Sources
  1.2 Walkthrough Organization
2 Quick Language Overview
3 Walkthrough of Hello World
  3.1 Quick Code Overview
  3.2 Hosting, Globals, and .NET Namespaces Access
    3.2.1 DLR Dynamic Binding and Interoperability -- a Very Quick Description
    3.2.2 DynamicObjectHelpers
    3.2.3 TypeModels and TypeModelMetaObjects
    3.2.4 TypeModelMetaObject's BindInvokeMember -- Finding a Binding
    3.2.5 TypeModelMetaObject.BindInvokeMember -- Restrictions and Conversions
  3.3 Import Code Generation and File Module Scopes
  3.4 Function Call and Dotted Expression Code Generation
    3.4.1 Analyzing Function and Member Invocations
    3.4.2 Analyzing Dotted Expressions
    3.4.3 What Hello World Needs
  3.5 Identifier and File Globals Code Generation
  3.6 Sympl.ExecuteFile and Finally Running Code
4 Assignment to Globals and Locals
5 Function Definition and Dynamic Invocations
  5.1 Defining Functions
  5.2 SymplInvokeBinder and Binding Function Calls
6 CreateThrow Runtime Binding Helper
7 A Few Easy, Direct Translations to Expression Trees
  7.1 Let* Binding
  7.2 Lambda Expressions and Closures
  7.3 Conditional (IF) Expressions
  7.4 Eq Expressions
  7.5 Loop Expressions
8 Literal Expressions
  8.1 Integers and Strings
  8.2 Keyword Constants
  8.3 Quoted Lists and Symbols
    8.3.1 AnalyzeQuoteExpr -- Code Generation
    8.3.2 Cons and List Keyword Forms and Runtime Support
9 Importing Sympl Libraries and Accessing and Invoking Their Globals
10 Type instantiation
  10.1 New Keyword Form Code Generation
  10.2 Binding CreateInstance Operations in TypeModelMetaObject
  10.3 Binding CreateInstance Operations in FallbackCreateInstance
  10.4 Instantiating Arrays and GetRuntimeTypeMoFromModel
11 SymplGetMemberBinder and Binding .NET Instance Members
12 ErrorSuggestion Arguments to Binder FallbackX Methods
13 SymplSetMemberBinder and Binding .NET Instance Members
14 SymplInvokeMemberBinder and Binding .NET Member Invocations
  14.1 FallbackInvokeMember
  14.2 FallbackInvoke
15 Indexing Expressions: GetIndex and SetIndex
  15.1 SymplGetIndexBinder's FallbackGetIndex
  15.2 GetIndexingExpression
  15.3 SymplSetIndexBinder's FallbackSetIndex
16 Generic Type Instantiation
17 Arithmetic, Comparison, and Boolean Operators
  17.1 Analysis and Code Generation for Binary Operations
  17.2 Analysis and Code Generation for Unary Operations
  17.3 SymplBinaryOperationBinder
  17.4 SymplUnaryOperationBinder
18 Canonical Binders or L2 Cache Sharing
19 Binding COM Objects
20 Using Defer When MetaObjects Have No Value
21 SymPL Language Description
  21.1 High-level
  21.2 Lexical Aspects
  21.3 Built-in Types
  21.4 Control Flow
    21.4.1 Function Call
    21.4.2 Conditionals
    21.4.3 Loops
    21.4.4 Try/Catch/Finally and Throw
  21.5 Built-in Operations
  21.6 Globals, Scopes, and Import
    21.6.1 File Scopes and Import
    21.6.2 Lexical Scoping
    21.6.3 Closures
  21.7 Why No Classes
  21.8 Keywords
  21.9 Example Code (mostly from test.sympl)
22 Runtime and Hosting
  22.1 Class Summary
23 Appendixes
  23.1 Supporting the DLR Hosting APIs
    23.1.1 Main and Example Host Consumer
    23.1.2 Runtime.cs Changes
    23.1.3 Sympl.cs Changes
    23.1.4 Why Not Show Using ScriptRuntime.Globals Namespace Reflection
    23.1.5 The New DlrHosting.cs File
  23.2 Using the Codeplex.com DefaultBinder for rich .NET interop
  23.3 Using Codeplex.com Namespace/Type Trackers instead of ExpandoObjects
  23.4 Using Codeplex.com GeneratorFunctionExpression


Other documents:

Dynamic Language Runtime
DLR Hostirng Spec
Expression Trees v2 Spec
Getting Started with the DLR as a Library Author
Sites, Binders, and Dynamic Object Interop Spec

Clone this wiki locally