Hosting IronPython in a C# application

Let’s use IronPython as a scripting language within our C# application.

Note: an application I work on uses an earlier version of IronPython and they’ve significantly changed the way you use it, so this information is only relevant for version 2.7.x of Python

What are we trying to do here ?

So, the premise is this. I have a C# application and I want to allow a user/ops or whoever to write scripts which can interact directly with the running instance of the application and/or the types within an assembly. Standard scripting fair I’m sure we’d all agree.

Let’s start off by downloading the package IronPython via NuGet. The version installed is 2.7.4.

To create the Python scripting engine we use the following

ScriptEngine engine = Python.CreateEngine();

We could alternatively create a runtime and then get the engine from it, as per

ScriptRuntime runtime = Python.CreateRuntime();
ScriptEngine engine = runtime.GetEngine("Python");

Now, unlike earlier versions of the scripting engine, it appears (unless I find information that differs from this view) that we no longer really have a global scope for writing our scripts but instead create a scope ourselves (i.e. in earlier versions we could add variables and execute code on what appeared to be a global level).

So we need to create a script scope using

ScriptScope scope = engine.CreateScope();

Now we can interact with this scope, importing modules, executing code and adding variables.

Allowing our scripts to use our assembly types

Let’s assume our application adds a bunch of types/classes that we might instantiate from IronPython. To make these available to the Python code we need to use the CLR from Python to add references to our assemblies, but in this instance we don’t want the script writer to have to do this every time, so we write

scope.ImportModule("clr");

engine.Execute("import clr", scope);
engine.Execute("clr.AddReference(\"MyAssembly\")", scope);
engine.Execute("from MyAssembly import *", scope);

In the above we import the CLR module into the scope object and then execute some Python code using the engine, against the scope we created. As eluded to, we could have written these commands in every Python script as per the code below

import clr
cl.AddReference("MyAssembly")
from MyAssembly import *

but I think you’d agree it’s much better to let the host setup this code instead, especially if we wanted to add lots of assemblies.

Now from Python we can instantiate classes from MyAssembly. For example, if my host application included an assembly called MyAssembly, with the following class definition

public class MyLibrary
{
   public void Run()
   {
      Console.WriteLine("MyLibrary run called");
   }
}

then, from Python we could write the following

r = MyLibrary()
r.Run()

Putting this altogether to show how we can reference an assembly and create the types within the said assembly, just create a console application project called ConsoleApplication and enter the following

public class Application
{
   public string Name { get { return "MyApp"; } }
}

class Program
{
   static void Main(string[] args)
   {
      string code = "app = Application()\n" +
                    "print app.Name";

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

      scope.ImportModule("clr");

      engine.Execute("import clr", scope);
      engine.Execute("clr.AddReference(\"ConsoleApplication\")", scope);
      engine.Execute("from ConsoleApplication import *", scope);

      ScriptSource source = engine.CreateScriptSourceFromString(code, 
                     SourceCodeKind.Statements);
      source.Execute(scope);
   }
}


What if we already have an instance of an object in our hosting application that we want to make available to IronPython ?

So in this instance we use the following, from the hosting C# application

scope.SetVariable("host", this);

where, in this example, we pass in an instance of the hosting application. Now from Python we can call methods on the variable host, i.e. assuming the hosting application has a method named ShowMessage, we could write the following

host.ShowMessage("Hello from Python")

Finally, let’s say we have everything setup and we have a text editor embedded in the hosting application with a run button, when run is pressed we want to run the code entered into the text editor, thus

var source = engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements);
source.Execute(scope);

the above code takes the source code (the code variable), then executes it in the given scope.

We could load the script from a file if we wished using

engine.CreateScriptSourceFromFile(filename);

where filename is string representing the location and filename of the Python script file.

Putting this all together into a simple example, just create a console application project and enter the following code

public class Application
{
   public string Name { get { return "MyApp"; } }
}

class Program
{
   static void Main(string[] args)
   {
      string code = "print application.Name";

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

      scope.SetVariable("application", new Application());

      ScriptSource source = engine.CreateScriptSourceFromString(code, 
                     SourceCodeKind.Statements);
      source.Execute(scope);
   }
}