Partager via


DLR Hosting in Silverlight

As you probably know, DLR is the dynamic language runtime that provides a common platform for dynamic languages and scripting in .NET. Their two main languages, IronPython and IronRuby, are available to develop your programs and also to be hosted in your programs. DLR hosting means that the users of your program can use scripting in any DLR language, for example to automate your program or to programmatically access the domain model of your application.

I was thinking about adding a capability to plot function graphs like y = cos(x) to my Live Geometry app, so I thought of hosting the DLR in Silverlight to compile and evaluate mathematical expressions.

Fortunately, DLR readily supports this scenario. And fortunately, Tomáš Matoušek, a developer on our IronRuby Team (part of Visual Studio Managed Languages), sits right around the corner from my office and was kind enough to provide great help when I had questions. Big thanks and kudos to Tomáš!

So, to host the DLR in Silverlight, here's the file that I added to my project (you can view my full source code here: https://dynamicgeometry.codeplex.com/SourceControl/ListDownloadableCommits.aspx).

All you need to do is to set up a script runtime, get your language's engine (here we use Python), create a scope for your variables and you're ready to evaluate, execute and compile!

 using System;
using DynamicGeometry;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Silverlight;

namespace SilverlightDG
{
    public class DLR : ExpressionCompiler
    {
        ScriptRuntime runtime;
        ScriptEngine engine;
        ScriptScope scope;

        public DLR()
        {
            var setup = new ScriptRuntimeSetup();
            setup.HostType = typeof(BrowserScriptHost);
            setup.LanguageSetups.Add(Python.CreateLanguageSetup(null));

            runtime = new ScriptRuntime(setup);
            engine = runtime.GetEngine("Python");
            scope = engine.CreateScope();

            engine.ImportModule("math");
        }

        public override Func<double, double> Compile(string expression)
        {
            var source = engine.CreateScriptSourceFromString(
                string.Format(@"
from math import *

def y(x):
    return {0}

func = y
", expression),
                Microsoft.Scripting.SourceCodeKind.File);

            CompiledCode code = source.Compile();
            code.Execute(scope);
            var func = scope.GetVariable<Func<double, double>>("func");
            return func;
        }
    }
}

ExpressionCompiler is my own abstract class that I defined in the DynamicGeometry assembly:

 using System;

namespace DynamicGeometry
{
    public abstract class ExpressionCompiler
    {
        public abstract Func<double, double> Compile(string expression);
        public static ExpressionCompiler Singleton { get; set; }
    }
}

As you see, the service that I need from the DLR is to implement the Compile method, that compiles an expression down to a callable function delegate, which I can then use to evaluate a function at a point.

Finally, just register the DLR as an implementation for my ExpressionCompiler:

 ExpressionCompiler.Singleton = new DLR();

And we're ready to go.

Let's go back to the DLR.cs and I'll comment a little more on what's going on. Essentially, to host the DLR you'd need 3 things:

 ScriptRuntime runtime;
ScriptEngine engine;
ScriptScope scope;

Runtime is your "world". You load a language-specific engine (like PythonEngine) into the runtime. To create a runtime with a language, one way is to use:

 var setup = new ScriptRuntimeSetup();
setup.HostType = typeof(BrowserScriptHost);
setup.LanguageSetups.Add(Python.CreateLanguageSetup(null));
runtime = new ScriptRuntime(setup);

This will work fine in Silverlight, because we use a browser-specific BrowserScriptHost, which does not use the file system. One problem that I had is that I was trying to directly call:

 runtime = Python.CreateRuntime();

Which didn't work because it used the default script host (which tried to access the file system) and not the BrowserScriptHost. After you have the runtime, you can get the engine and create a scope in that engine:

 engine = runtime.GetEngine("Python");
scope = engine.CreateScope();

Now you're ready to do things like:

 var five = engine.Execute("2 + 3", scope);

You can go up to the first code example to see how I declared a function in Python, and converted it to a C# callable Func<double, double> delegate.

Finally, here's the working application (which you can also find at https://geometry.osenkov.com). Press the y = f(x) toolbar button, enter sin(x) and press the Plot button:

Comments