The Python Powered Turtle

A Logo Like Turtle Moved by the Power of Embedded IronPython

The logo-like-turtle moved by the power of Python

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

Adding user scripting to an application with IronPython, or any of the Dynamic Language Runtime based programming languages, can be really easy - amounting to nothing more than a few lines of code in some cases.

This example was originally created by Lee Holmes, as an example of embedding Powershell in .NET applications. Thanks to Marc The Powershell Guy for showing me this example.

The Turtle

The essence of the program is that we have a logo like 'turtle' on screen. We can move the turtle around with the buttons on the user interface, and if his pen is down he will draw as he moves.

The marvellous moving, drawing turtle

As well as buttons to directly control the turtle, we have the scripting interface to control the turtle. If you select the 'Script' tab of the TabControl then you will see a list box containing a choice of example scripts, along with a text box and a 'Run' button. Selecting any of the examples in the list box loads them into the text box, where you can edit them or just type your own code.

The scripting interface to the turtle

When the 'Run' button is pressed, the code in the text box is executed. Any movements or commands to the turtle are recorded, and when the script ends the result is drawn to the screen.

Note

The examples are loaded from the examples folder when the application starts. You can add new examples by saving them as text files in that folder. The title in the list box is taken from the filename.

Commands are given to the turtle by calling methods on the turtle object. Available methods include:

  • Reset()
  • PenUp()
  • PenDown()
  • Forward(distance)
  • Backward(distance)
  • Left(degrees)
  • Right(degrees)

From these simple commands you can create some complex patterns. It can even be used as a serious system administration tool - see the example that draws a graph of the working set memory used by all currently running processes!

Creating the Python Engine

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

...

    ScriptEngine engine;
    ScriptScope scope;

    private void InitializeCustom()
    {
        InitializeCanvas();
        this.tabControl.Focus();

        engine = Python.CreateEngine();
        scope = engine.CreateScope();

        ScriptRuntime runtime = engine.Runtime;
        runtime.LoadAssembly(typeof(String).Assembly);
        runtime.LoadAssembly(typeof(Uri).Assembly);
    }

When the application starts InitializeCustom is called. As well as initialising part of the UI we create a Python engine (the ScriptEngine) which we will use to execute the scripts, and a scope (ScriptScope) which is the namespace in which the code will be executed.

As we want the scripts to have access to the .NET types and classes contained in System.dll and mscorlib.dll, we need to add references to these two assemblies. This is something that the interpreter does for you if you run Python programs with ipy.exe, but we have to do it manually in a hosted environment.

Adding references to assemblies is done with the two calls to LoadAssembly on the ScriptRuntime.

Execution and Error Handling

This is the code in the 'Run' button handler. It is responsible for actually executing the scripts.

scope.SetVariable("turtle", turtle);
string code = scriptText.Text;
try
{
    ScriptSource source = engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements);
    source.Execute(scope);
}
catch (Exception ex)
{
    ExceptionOperations eo;
    eo = engine.GetService<ExceptionOperations>();
    string error = eo.FormatException(ex);

    MessageBox.Show(error, "There was an Error",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
    return;
}

savedImage = new Bitmap(this.pictureBox.Image);
turtle.Draw();
this.pictureBox.Refresh();

The first step is very important: scope.SetVariable("turtle", turtle);

This sets the turtle object into the ScriptScope we created earlier, with the name "turtle". This means that our scripts have access to this object as the "turtle" variable. Effectively this is how we publish our 'object model' to the hosted code - by putting an object into the scope that records how the user code uses it.

To execute the script we fetch the text from the TextBox as a string and turn it into a ScriptSource. This is then executed 'in the scope'.

Once we have done this, we can draw the image and refresh the GUI to display it.

Another important detail is the error handling. As we are executing arbitrary user code we need to be prepared to handle exceptions this code may raise. We do this using the ExceptionOperations to produce a nicely formatted Python traceback, and displaying any errors in a message box.

Here is what happens if we try to call a non-existent method on the turtle:

Turtle goes kablooie

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...