Timmy's Blog

Embedding IronPython in a C# application

 

1/02/2010 14:27:26

For reasons explained in my previous post, I decided to embed IronPython.

In this post I’m going to show you how to do it yourself. In order to get started
you’ll need a build of IronPython. If you’re targeting .NET 4.0 Beta 1 you’ll have to
get “IronPython 2.6 CTP for .NET 4.0 Beta 1”, otherwise you can get the normal release.
(The new features of C# 4.0 do make it a lot easier to embed dynamic languages).


First some background information. We have a framework targeting .NET 3.5 which
abstracts away many complexities for interacting with the hardware. It’s powered by
the Managed Extensibility Framework making it possible for developers to replace almost
every part of the framework with their own implementations. For example, we have an
interface called IDataTransportService which represents classes that know how to
send and receive binary data across a specific communication channel. By default
we ship two implementations: one for TCP/IP and another for serial communication.
These implementations, called ‘parts’ in the MEF world can be written in any CLR/DLR language.
In fact we even have a few parts that were written in F# because it is better suited than C# in those particular situations. Our partners can use the framework to develop their own
applications and in theory we could also provide it to our customers. However our customers are usually not developers so it would be of little use to them. Instead we want to provide
them with a small DSL inside our software allowing them to easily add new functionality on the fly. Python is easy to learn and we provide some helpers to make it even easier.

Let’s get started. First of all, you’ll have to add some references to the required assemblies:

- IronPython.dll
- IronPython.Modules.dll
- Microsoft.Dynamic.dll
- Microsoft.Scripting.dll

All of these are part of the IronPython package and you’ll find them in the installation directory. In my case this was ‘C:\Program Files (x86)\IronPython 2.6 CTP for .NET 4.0 Beta 2’.

Import the following namespaces:

using Microsoft.Scripting;
using IronPython.Runtime;

Next you’ll need to get an instance of the Python language engine:

var engine = Python.CreateEngine();

Now you can actually go ahead and run some Python code:

engine.Execute("print 'hello world'");

This will print ‘hello world’ to the output window or console, but it’s hardly impressive.

To make it a little more interesting, create a form (swf) or window (wpf) with 2 TextBoxes.
Give them meaningful names such as “txtScript” and “txtOutput”. (I can’t help it, I still prefix controls…).

This time we want the python code to be loaded from ‘txtScript.Text’ and we want to redirect
the output to the ‘txtOutput’ TextBox. Create a new class which inherits from MemoryStream:

internal class TextBoxStream(TextBox target) : MemoryStream
{
	TextBox target;
	
	public TextBoxStream(TextBox target)
	{
		this.target = target;
	}

	public override void Write(byte[] buffer, int offset, int count)
	{
		string output = Encoding.UTF8.GetString(buffer, offset, count);
		target.AppendText(output);
	}
}

Update the code to redirect the Output and Error streams from the Python language engine:

var outputStream = new TextBoxStream(txtOutput);
var engine = Python.CreateEngine();

engine.Runtime.SetOutput(outputStream,Encoding.UTF8);
engine.Runtime.SetError(outputStream,Encoding.UTF8);

engine.Execute(txtScript.Text);

Now, any output/error generated by the Python code in ‘txtScript’ will be added to ‘txtOutput’.

Next you’ll need a way to pass variables from the C# application into the Python script.
You do this by creating an instance of ‘ScriptScope’ and calling the SetVariable method.

var scope = engine.CreateScope(); 
scope.SetVariable("x",5); 
scope.SetVariable("y",2);

When executing the script, you’ll have to indicate you want to use the scope which will allow the Python script to access that variable. Consider the following example:

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

scope.SetVariable("x",5);
scope.SetVariable("y",10);

engine.Execute("print x * y",scope);

The output will be the result of x * y (which is 50 by the way).

Exceptions generated by the Python code will bubble up into your C# application,
so you’ll have to handle them carefully.

The easiest way is surround the code with a try…catch… block.
I suggest you catch at least the following exceptions:

- SyntaxErrorException
- UnboundNameException
- ArgumentTypeException

(There is more than one way to handle exceptions generated by the Python code and method we use here might not be the best one for your own situation.)

You can also get the python script directly from a file:

engine.Runtime.UseFile(filename);

Both engine.Execute() and engine.Runtime.UseFile() return a ‘dynamic’ in C# 4.0,
but for reasons I have not yet been able to figure out, engine.Execute() always returns null.

This is interesting because it allows you to ‘use’ the Python script as an object in C#.
Consider the following Python example:

import sys

def HelloWorld():
	print "Hello World"

def Foo():
	print "Foo"

If you tell the runtime to use this file you can do something like this:

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

dynamic theScript = engine.Runtime.UseFile(filename);

theScript.HelloWorld();
theScript.Foo();

As you can see there is really nothing complicated about embedding IronPython.
I don’t know of any best practices to use this yet so you’ll have to use your imagination.

Here is how we use it.

In our framework there is a ControllerClient class which can be used to interact with the hardware. It requires quite a bit of configuration (such as TCP/IP or serial configuration) and
it would be cumbersome if we had to do this in the Python script. Since the user has already
done the configuration in the main application we simply pass the current instance of the
ControllerClient into the script along with instances of some helper classes.

var engine = Python.CreateEngine();
engine.Runtime.IO.SetOutput(textBoxStream, Encoding.UTF8);
engine.Runtime.IO.SetErrorOutput(textBoxStream, Encoding.UTF8);
engine.Runtime.LoadAssembly(typeof(TextField).Assembly);
var scope = engine.CreateScope();

scope.SetVariable("controller", controller);
scope.SetVariable("properties", properties);
scope.SetVariable("factory", factory);
scope.SetVariable("alignment", alignment);
scope.SetVariable("scroll", scroll);
scope.SetVariable("clockformat", clockformat);

execution = engine.Execute(txtScript.Text, scope);

The first thing to note here is the line:

engine.Runtime.LoadAssembly(typeof(TextField).Assembly);

This loads an assembly into our Python engine, without this the user would have to manually add a reference to that assembly in the Python code like this:

import clr
clr.AddReference("Vialis.Led.Interfaces")

Next we set some variables in the scope. The controller is the instance of ControllerClient,
properties exposes some fields, factory exposes some methods and the remaining variables
are wrappers around some of the enums.

The ‘factory’ contains wrapper methods to create instances of different types we use in the framework. While the user could easily creates these instances manually in Python, we use this to make it look more like a language feature. An example method:

public TextField CreateTextField(byte id, ushort x, ushort y, ushort width, ushort height, TextAlignment alignment = TextAlignment.Left, TextScroll scroll = TextScroll.Auto, string font = "arial")
{
    return new TextField()
    {
        Id = id,
        X  = x,
        Y = y,
        Width = width,
        Height = height,
        Alignment = alignment,
        Scroll = scroll
    };
}

Granted, the argument list for this method is long, but that’s partly because we’re using 2 new features in C# 4.0 called optional and named parameters.  Basically it allows us to specify defaults for some of the parameters that will be used if no values have been passed to the method. And since these features are also supported by IronPython the users can now
write code like this in the script:

f1 = factory.CreateTextField(1,10,10,20,100, scroll = scroll.NoScroll)
f2 = factory.CreateTextField(2,50,50,20,100, font = "arial", scroll = scroll.Auto)
f3 = factory.CreateTextField(2,90,95,30,110);

(The short names f1, f2, f3 are only used to make it fit on this blog page :p)

The ‘scroll.NoScroll’ notation is possible because of the wrappers round the enums I mentioned earlier. For example the wrapper around the TextScroll enum looks like this:

public class TextScrollObject
{
    public TextScroll Auto { get { return TextScroll.Auto; } }
    public TextScroll NoScroll { get { return TextScroll.NoScroll; } }
    public TextScroll StartWithText { get { return TextScroll.StartWithText; } }
    public TextScroll StartEmpty { get { return TextScroll.StartEmpty; } }
}

Every method returns one of the values of the enum, pretty useless on its own but it does
make it a little easier for the user to write their scripts.

Finally we use AvalonEdit as a nice editor with code completion capabilities.
There is an excellent article written by Daniel Grunwald on TheCodeProject which tells you exactly how to use and extend AvalonEdit. In our system it looks like this:

(Don’t mind the looks, we don’t have any designers on this and it’s a draft.)

Well that’s it… as you can see it’s very easy to embed and ‘extend’ IronPython.
You can probably think of a few places where it might be useful and if you can’t
just make something up, because it’s really cool to play with :p


 
Your comments:
Your details:
 
   
 

Since you have not authenticated,
we require you to submit some
additional information and fill out the captcha.

Your e-mail address will not be disclosed to anyone and will not be visible on the site. If you specify your blog, we will display a link to it.

If you sign in with your OpenID, you can store your profile and you will never have to enter your details or fill in the captcha again.