The IronPython Calculator and the Evaluator

Practical Examples of the DLR Hosting API

Exposing a scripting API

 

 

Note

This is part of a series of articles on embedding IronPython:

For a much more in depth introduction to hosting and interacting with dynamic languages from .NET applications see chapters 14 & 15 of IronPython in Action.

Introduction

These examples were written for a talk given at the ACCU Conference 2008 on IronPython & Dynamic Languages on .NET. They illustrate different ways of embedding IronPython 2 in .NET applications.

Both examples are in C# and downloadable as Visual Studio 2008 projects (including compiled binaries).

My First Calculator

My first calculator

This is a very simple example of embedding IronPython. CLicking buttons in the user interface adds numbers to the textbox. Clicking on the equals button passes the whole expression to an IronPython engine which evaluates it. It places the result in the text box as a string.

Here's a break down of the code. Some or all of the following using directives will be needed at the start of your code (after adding references to the IronPython 2 binaries):

using IronPython;
using IronPython.Hosting;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

We create the Python engine using the convenience class Python. If we were embedding IronRuby we would use the Ruby class that comes with the IronRuby assemblies.

private ScriptEngine engine;
private ScriptScope scope;

public Engine()
{
    Dictionary<String,Object> options = new Dictionary<string,object>();
    options["DivisionOptions"] = PythonDivisionOptions.New;
    engine = Python.CreateEngine(options);
    scope = engine.CreateScope();
}

This uses two of the basic (and most important) components of the Dynamic Language Runtime hosting API. Calling Python.CreateEngine() returns us a ScriptEngine specialised for the Python programming language. The ScriptScope represents a namespace in which we can execute code.

The dictionary of options that we pass to the CreateEngine call switches on Python True division, which is necessary for a calculator or 1/2 would be equal to 0! If we didn't need true division (also known as new style division) then we could omit the options altogether.

When the 'equals' button is pressed on the calculator, the calculate method of the engine is called, taking the string from the textbox. This string is evaluated in the scope as an expression using the following code:

public string calculate(string input)
{
    try
    {
        ScriptSource source =
            engine.CreateScriptSourceFromString(input,
                SourceCodeKind.Expression);

        object result = source.Execute(scope);
        return result.ToString();
    }
    catch (Exception ex)
    {
        return "Error";
    }
}

The expression either evaluates to a result (an object), which is then turned to a string with ToString and returned, or an error occurs. The exception handling here is a bit 'broad'... Smile

The next example is not much more complex but a bit more practical.

The IronPython Evaluator

Exposing a scripting API

The evaluator places a couple of objects into the execution scope. When the execute button is pressed, the user code is executed.

Because we don't need to switch on true division, the code that creates the Python engine is even simpler:

private ScriptEngine engine;
private ScriptScope scope;
public Engine()
{
    engine = Python.CreateEngine();
    scope = engine.CreateScope();
}

One of the objects we place in the scope is the button from the user interface - and the user code can add events to its Click handler. After execution the value of the variable 'x' is pulled out of the execution context and displayed in a text box (where errors are also shown).

This (code shown below) illustrates a couple of different ways of exposing a scripting API or plugin object model to users. When you publish objects to the execution context the user code can interact with them - and let user code add events (you call their code by firing the events - illustrated here by pressing the button). Alternatively you could expect user code to define a class or object with a specific name and then pull it out of the scope.

Unfortunately IronPython classes are not .NET classes (because you can weird things with dynamic classes like add and remove methods or change the base types - plus .NET classes can't be garbage collected). Working with dynamic objects (classes and instances) directly from C# is painful because you have to perform every step manually (See the Hosting API article for examples on performing dynamic operations through the DLR from C# or VB.NET). CLR support for dynamic objects is being added in .NET 4 (C# 4 and Visal Basic 10). The compiler could automatically generate the reflection calls for you when you work with dynamic objects.

Currently you can get round this by subclassing a .NET class and casting the object as you fetch it out. That way the compiler knows what members are available on your dynamic object.

Here's the code:

public string evaluate(string x, string code)
{
    scope.SetVariable("x", x);
    scope.SetVariable("button", this.button);

    try
    {
        ScriptSource source = engine.CreateScriptSourceFromString(code,
            SourceCodeKind.Statements);

        source.Execute(scope);
    }
    catch (Exception ex)
    {
        return "Error executing code: " + ex.ToString();
    }

    if (!scope.VariableExists("x"))
    {
        return "x was deleted";
    }
    string result = scope.GetVariable<object>("x").ToString();
    return result;
}

Notice that the ScriptSource is created with SourceCodeKind.Statements instead of SourceCodeKind.Expression. Because of this, we no longer expect the call to source.Execute(scope) to return anything useful.

In this example we still fetch the variable 'x' out of the scope as object, this time using the generic version of the GetVariable method. We could specify a different type (i.e. a known .NET type) in which case we would have to have error handling around it if the cast fails (the error handling is only really necessary if we are executing arbitrary user code).

This of course has only been a skin deep exploration of the DLR hosting API, but hopefully it gives you somewhere to start.

For a slightly more detailed look, especially at some of the issues around exposing an API to your hosted code, read the article Embedding the Dynamic Language Runtime.

For the full details you can either refer to the source code (both IronPython and the DLR are Open Source), or read the DLR hosting spec documentation: DLR Project Page on Codeplex

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Fri Nov 27 18:32:35 2009.

Counter...