Immediate Window. Generating and executing lambdas across application domains.


First I’d like to say thank you words to Igor Moochnick. His article CodeDom extensions and dynamic LINQ (string/script to LINQ emitting) gave me a lot of pleasure extending it a bit and creating shorter code in a Linq manner flow. Refer to his article for more details on CodeDom and extension methods he introduced.

Introduction

Due to some purposes I needed creating a quick implementation of some kind of "Immediate Window" that could allow me executing methods and setting properties for object instances at live running application. I tried several approaches. Among them were "Dynamic Methods", extending "Linq Dynamic API" and parsing commands and property paths to be able using them against reflection. But at last moved to code generation as the most easiest and quickest way of accomplishing the task.

Imagine you have a small TextBox based pad in the running WPF application similar to "Immediate Window" pad in Visual Studio and you are willing to execute the following snippet against the main application window:

this.Title = "NewTitle"

(this.FindName("btnExecute") as Button).SetValue(Button.ContentProperty, "NewContent")

(this.FindName("stackPanel") as StackPanel).Children.Add(new Button() { Content="xxx" })

So these lines should be transformed into the code from their string representation and executed in runtime against the running Window as the code is supposed to perform some actions against the Window instance.

What I’ve decided to do was similar to the following ideas:

1. We should have the dynamically generated method that will have our text line as it’s body (though with some possible transformations)

2. Method should accept our Window instance as parameter and perform defined text line against parameter.

3. It should return something if we are requesting the property value.

So we need here something similar to the code we could write down manually in the application wrapped into a method, for example

this.Title = "NewTitle" can be imagined as following:

public static void SetTitle(Window obj, string value)
{
  obj.Title = value;
}

So upon processing the command our environment has to generate some method called for example "SetTitle", provide the execution context (in this case a Window) and pass the command typed by the user. During that it should redirect all "this" aliases to the execution context detected (in this case all "this" should be translated into "obj"). Regard this as you are typing the body of some method without body declaration and are sure that all the missing stuff will be automatically generated.

Implementation

Code generation for this case will require dealing with separate application domains due to assembly management issues and possible memory leaks. The steps will be as following:

1. Transform and wrap user defined method/function body with appropriate method, class, namespace, etc. declarations using the CodeDom (so to get the ready to compile sources either into memory or physical file)

2. Compile the resulting Dom into the assembly (I’ll be using physical files)

3. Create a separate application domain and load compiled assembly into it.

4. Extract the generated method from the loaded assembly and invoke it against the execution context (in this case it will be a Window)

5. Free resources, unload second application domain and perform a temporary files cleanup.

This is similar to implementing an Add-In or Plug-In support for your application where your external algorithms may be added to main application, executed against some provided execution context and finally destroyed without any impact on main application.

It is obvious that you might don’t want passing whole your window across application domain boundaries (i.e. .net Remoting scenario) and you don’t want marking anything as Serializable to pass into another domain. So all we need here is getting some delegate or lambda somewhere outside for applying it against our Window locally.

Void execution

Under Void execution I mean some operation that is invoked against your execution context and doesn’t return any results. For example setting the property value.

So what happens when user types this.Title = "New Title" and presses Enter or some "Execute" button

1. "this" alias is converted into "context"

2. Lambda action is generated that performs your logic against some "context" parameter:

Action<T> action = context => { context.Title = "New Title"; };

3. Wrapper method and class container is generated that will help you getting lambda from another domain:

namespace GraphSquare.AutoScript
{
  using System;
  using System.Windows;

  public class Script
  {
    public static Action<T> CreateLambda<T>() where T : Window
    {
      Action<T> action = context => { context.Title = "New Title"; }; 
      return action;
    }
  }
}

As you can see this method will be valid for all the types based on Window, so you’ll have possibilities running the resulting lambda against your different custom windows and dialogs that inherit Window type. Note that this rules are introduced for clarification purposes and <T> constraint may differ. According to this constraint we ensure that the method will be compiled successfully as each Window object has a Title property.

CodeDom for this method is generated using the Igor’s CodeDom extensions I mentioned at the beginning of the article:

public static CodeDom GenerateActionDom<T>(string actionBody)
{
  string body = string.Format(
    "Action<T> action = context => {{ {0}; }}; return action;",
    actionBody.Replace("this", "context"));

  CodeDom c = new CodeDom();
  c.AddReference(DefaultReferences)
      .AddNamespace(DefaultNamespace)
        .Imports(DefaultImports)
          .AddClass(
            c.Class(DefaultClassName)
              .AddMethod(c.LambdaAction("CreateLambda", typeof(T).Name, null, body))
           );

  return c;
}

I won’t dwell on extension methods implementation and my contribution to it as it is more understandable when looking through the sources. Please refer to the source code link at the end of this article. In two words you are creating the source for the class mentioned earlier and specifying the <T> constraint passing the generic type provided for the method. Also "this" alias is converted into "context" and passed to CodeDom as a body of newly generated method.

Function execution

If you want invoking the command that user had typed and expect some results to be returned and displayed you might want in this case getting Func<T,K> lambda, running it against some T parameter and present the K result back to user. For simplicity purposes I’ve decided to reuse native .net "return" keyword and so being close to method body declaration in a common way it is used.

So what happens when user types return this.Title and presses Enter or some "Execute" button:

1. "this" is converted into "context"

2. Lambda function is generated that performs some logic against your "context" and returns results

Func<T, object> func = context => { return context.Title; };

3. Wrapper function is generated for getting lambda from another domain, constraints and types are defined according to the calling application

namespace GraphSquare.AutoScript
{
  using System;
  using System.Windows;

  public class Script
  {
    public static Func<T, object> CreateLambda<T>() where T : Window
    {
      Func<T, object> func = context => { return context.Title; };
      return func;      
    }
  }
}

Here’s how CodeDom is generated to get such a class:

public static CodeDom GenerateFunctionDom<T>(string fucntionBody)
{
  string body = string.Format(
    "Func<T,object> func = context => {{ {0}; }}; return func;",
    fucntionBody.Replace("this", "context"));

  CodeDom c = new CodeDom();
  c.AddReference(DefaultReferences)
      .AddNamespace(DefaultNamespace)
        .Imports(DefaultImports)
          .AddClass(
            c.Class(DefaultClassName)
              .AddMethod(c.LambdaFunction("CreateLambda", typeof(T).Name, null, body))
           );

  return c;
}

Compilation and assembly loading

I’ve provided additional method for CodeDom class providing capabilities compiling sources to file without keeping the strong reference to Assembly file at the end so providing possibilities of removing the assembly later on when it is no more in use. You’ll be calling CodeDom.CompileAssembly method that gets string parameter for full final assembly path and returns CompilerResults for results processing in order compilation exceptions.

According to common approach patterns for loading assemblies inside separate application domains you need implementing a proxy Loader class to serve also as a contract and bridge between your two domains. So exactly the loader being instantiated in the second application domain will load compiled script assembly, extract the desired lambda and execute it against your context or simply return you the lambda so you can execute it inside the main application domain whenever required.

Assembly Loader

using System;
using System.IO;
using System.Reflection;

namespace GraphSquare.Scripting.CodeDom
{
  [Serializable]
  public class AssemblyLoader
  {
    Assembly assembly;

    public AssemblyLoader()
    {
      assembly = null;
    }

    // Loads the content of a file to a byte array to
    // prevent file locking, so user can remove or modify assembly 
    // while using it loaded into memory
    static byte[] LoadFile(string filename)
    {      
      FileStream fs = new FileStream(filename, FileMode.Open);
      byte[] buffer = new byte[(int)fs.Length];
      fs.Read(buffer, 0, buffer.Length);
      fs.Close();
      return buffer;
    }
    
    public void Load(string assemblyPath)
    {
      byte[] rawAssembly = LoadFile(assemblyPath);
      this.assembly = Assembly.Load(rawAssembly);
    }
  }
}

This is a basic implementation providing only assembly loading facilities. It will be extended later on. Note that it is situated in the separate assembly in order to serve a contract between different domains.

To create additional application domain our main application will have the following method:

private static AppDomain CreateNewDomain()
{
  // Create a simple application domain
  return AppDomain.CreateDomain(
    typeof(AssemblyLoader).Name, 
    AppDomain.CurrentDomain.Evidence, 
    AppDomain.CurrentDomain.SetupInformation);
}

This is the most basic implementation of second domain that takes Evidence and Setup Information from the main one and so loads all the references that the main domain has. In our case it will also load the contract library with the AssemblyLoader so we could establish communication between domains easily.

To create a valid loader in the second domain and loading external assembly file into it we’ll be using the following method:

private static AssemblyLoader CreateLoader(AppDomain domain, string assemblyPath)
{
  // Create a new instance of AssemblyLoader in the separate domain
  AssemblyLoader loader = domain.CreateInstanceAndUnwrap(
    "GraphSquare.Scripting", 
    typeof(AssemblyLoader).FullName) as AssemblyLoader;

  // Load compiled assembly into the loader
  loader.Load(assemblyPath);      
  return loader;
}

Extracting compiled lambdas

Fine now we have two possible members of the generated wrapper class needed to be discovered and loaded (action and function) to be used in lambda generation. As we have a separate domain with compiled assembly loaded into it and know exactly the namespace, type and name of the wrapper class and even the name of the method to generate lambda – "CreateLambda" (as we did it’s generation actually) the process of it’s loading and invokation becomes quite trivial.

Using Assembly.GetType(string…) method it becomes easy loading types and activating objects. For each type you can call GetMethod(…) to get the necessary method info to be used in invokaction. Thus discovering and invoking generic methods are a bit different. Here’s how can extract our generated "CreateLambda" method from AssemblyLoader:

public Action<T> GetLambdaAction<T>(string type, string action)
{
  // Extract type method info from assembly
  MethodInfo methodInfo = assembly.GetType(type).GetMethod(
    action, 
    BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

  // Create generic arguments for generic method
  Type[] genericArguments = new Type[] { typeof(T) };

  // Create generic function info for invokaction
  MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

  // Invoke generic function and get return value as Action<T>
  Action<T> returnValue = (Action<T>)genericMethodInfo.Invoke(null, null);

  // return results
  return returnValue;
}

As compiled assembly is loaded into the second application domain where the AssemblyLoader is already present so loader is capable of discovering our generated type to work with further.

When executed this method returns us ready to use lambda with the body we defined earlier. In most of the cases guess you’ll have to get compiled lambda, execute it against some context and dispose it. So we can introduce two more members for the AssemblyLoader for invoking actions and functions directly from AssemblyLoader.

public bool InvokeLambdaAction<T>(string type, string action, T argument)
{      
  // Extract type method info from assembly
  MethodInfo methodInfo = assembly.GetType(type).GetMethod(
    action, 
    BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

  // Create generic arguments for generic method
  Type[] genericArguments = new Type[] { typeof(T) };

  // Create generic function info for invokaction
  MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);

  // Invoke generic function and get return value as Action<T>
  Action<T> returnValue = (Action<T>)genericMethodInfo.Invoke(null, null);
  if (returnValue != null)
  {
    // Execute received action against argument provided
    returnValue(argument);
    return true;
  }
  return false;
}

public object InvokeLambdaFunc<T>(string type, string function, T argument)
{
  // Extract type method info from assembly
  MethodInfo methodInfo = assembly.GetType(type).GetMethod(
    function, 
    BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);

  // Create generic arguments for generic method
  Type[] genericArguments = new Type[] { typeof(T) };
  // Create generic function info for invokaction
  MethodInfo genericMethodInfo = methodInfo.MakeGenericMethod(genericArguments);
  // Invoke generic function and get return value as Func<T,K>
  Func<T, object> func = (Func<T, object>)genericMethodInfo.Invoke(null, null);
  //return execution result of func against the argument provided
  return func(argument);
}

As you can see both methods are not so different. Both of them require full type name of the class holding the method, method or function name to be searched and argument of your needed type to be processed as an execution context.

So the process of executing user typed command that doesn’t require results can be as following (at main application side):

// Generate CodeDom
CodeDom c = CodeDomSmith.GenerateActionDom<T>(methodBody);
string assemblyPath = GenerateAssemblyPath();
// Compile sources into assembly
CompilerResults results = c.CompileAssembly(assemblyPath);

// Process compilation errors
if (results.Errors.Count > 0)
  return false;

// Create separate application domain
AppDomain domain = CreateNewDomain();
// Create loader proxy based on application domain created 
// and load compiled assembly to it
AssemblyLoader proxy = CreateLoader(domain, assemblyPath);

try
{
  // Extract and execute lambda against the context object
  proxy.InvokeLambdaAction<T>(
    CodeDomSmith.DefaultClassFullname, "CreateLambda", context);
}
catch (Exception ex)
{
  return false;
}

if (domain != null)
{
  // Unload and dispose second application domain
  AppDomain.Unload(domain);
  domain = null;
}
if (File.Exists(assemblyPath))
  // Remove used assembly
  File.Delete(assemblyPath);

The only difficulty that arise is determining what type of lambda to generate according to the user input. I mean how to decide whether he wants executing a Void or getting some results. I’ve accomplished that again as I’ve mentioned earlier by reusing the "return" keyword. So due to this sample when the user starts the command from "return" keyword we are generating the Func<T,K> otherwise Action<T>

Samples:

Here’s the sample environment I’ve prepared for the article:

image

You’ll have the Window that will be assigned as an execution context for all upcoming commands typed in the bottom TextBox control.

"Execute" button compiles and executes the command putting all the output to the "Debug window" TextBox control. You will also be able to see the content of the class generated and executed for development purposes.

"Help" button shows a list of dummy samples that can be copy-pasted and tested against the window.

"Execution Result" TextBox shows the results of the command execution. This can be either "void()" if method was compiled and executed or some value.

Here’s the possible result of execution of the command:

this.Title = "GraphSquare Scripting"

image

Note that execution result is "void()" as we didn’t try to return anything and the Title of the window has changed from "Window1" to "GraphSquare Scripting"

Let’s try running the application once again and executing the following command:

return this.Title == "Window1"

image

As you can see command was successfully evaluated and returned "True" value that is a valid result as the Window’s Title property value is "Window1"

Now let’s try typing a command that cannot be valid and compiled by default, something like

this.ExecuteMissingCommand()

If you remember the execution context for our commands is being set to Window. This means that as a common Window class does not contain the method typed the resulting code won’t be compiled and cannot be executed against a window. This brings to a compilation exception:

image

The exception message will be the following:

error CS1061: ‘T’ does not contain a definition for ‘ExecuteMissingCommand’ and no extension method ‘ExecuteMissingCommand’ accepting a first argument of type ‘T’ could be found (are you missing a using directive or an assembly reference?)

This is mainly for your debugging purposes. The real life implementation can hide the developer oriented details and present more user-friendly message.

Also as you might have noticed all lambda generation methods are generic ones, for example

public static bool InvokeContextMethod<T>(T context, string methodBody, ...)

this means that you can specify different execution contexts or even change them on the fly.

As for the suggestions I think there could be some contracts to serve as execution context instead of applications or windows. Remember not providing the end-user more power that he is expected to have. As you will see from the samples provided within the "Help" section of this tool you can simply add or remove new elements to the window, rename buttons, etc.

Hope this helps someone enriching his tools with runtime command support or Debug features.

The source code for the article can be found here

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s