Embedding IronPython 2

Examples of the DLR Hosting API

Exposing a scripting API

 

 

Introduction

These examples were written for a talk I'm giving at the ACCU Conference 2008 on IronPython & Dynamic Languages on .NET.

The talk includes two examples of embedding IronPython. Up until now the DLR hosting API has undergone major changes with almost every release. With the latest IronPython 2 release, the DLR is now in beta and is approaching the kind of stability outlined in the (79 pages of complexity) now fairly out of date DLR Hosting Spec.

Both examples are in C# and downloadable as Visual Studio 2008 projects (with binaries). Because IronPython 2 Beta 1 doesn't yet work with Mono, they are Windows only for the moment...

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 equals passes the whole expression to an IronPython engine that evaluates it as an expression. 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.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;

This is the constructor of my Engine class, which creates the IronPython engine and an execution scope:

private ScriptEngine engine;
private ScriptScope scope;

public Engine()
{
    engine = ScriptRuntime.Create().GetEngine("py");
    // An alternative would be engine = PythonEngine.CurrentEngine

    scope = engine.CreateScope();

    ((IronPython.PythonEngineOptions)engine.Options).DivisionOptions =
            IronPython.PythonDivisionOptions.New;

}

The last call switches on Python True division, which is necessary for a calculator or 1/2 would be equal to 0!

Replacing GetEngine("rb"); (with references to the appropriate binaries) would create an IronRuby engine and execution scope.

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 'executed' in the scope as an expression using the following code:

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

        return source.Execute(scope).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 of a bit more practical use.

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. One of the objects is the button - 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 illustrates a couple of different ways of exposing a scripting or plugin API to users. Either publish objects to the execution context 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.

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 do it through reflection. CLR support for dynamic objects will be added. 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.

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

For buying techie books, science fiction, computer hardware or the latest gadgets: visit The Voidspace Amazon Store. If you're looking for a new techie job, try the Voidspace Tech Job Board. This is part of the Hidden Network of technology and programming jobs.

Hosted by Webfaction

Return to Top

Page rendered with rest2web the Site Builder

Last edited Sun Mar 30 22:19:23 2008.


Counter...


Voidspace: Cyberpunk, Technology, Fiction and More
Search this Site:
 
Web Site

IronPython in ActionIronPython in Action

Blogads

Follow me on:

Twitter

Pownce

Jaiku

Del.icio.us

Shared Feeds

Tech Jobs

Hidden Network

Tech Jobs Board

Hosting for an agile web