A recurring topic on the IronPython mailing list is the subject of hosting IPy in another application. Part of the reason the topic comes up frequently is that we keep changing the design (we're almost done doing that...we hope).
I thought it might be helpful to discuss where our hosting design has been, where it is, and where it's going within in the context of one consistent example. To that end this is the first in a series of three posts that will focus on one host showing the following basic concepts:
I'll start by discussing IronPython 1.1 in this entry, current 2.0 Alphas in the next, and finish with the model coming in future 2.0 Alphas (likely >7).
IronPython 1.1 is the latest production version. Released back in April 2007, this is the last version of IronPython not to be based on the Dynamic Language Runtime. Meaning, this hosting model was designed primarily to support the hosting of IronPython only, and there is no Microsoft.Scripting.dll.
Here's our example host in C#:
using System; using IronPython.Hosting; //From IronPython.dll namespace Hosting1 { class Host { public static void Main() { //Create a PythonEngine to execute code //and an EngineModule to execute in. PythonEngine pe = new PythonEngine(); EngineModule mod = pe.CreateModule("mymod", true); //Expose host data to IronPython and vice versa mod.Globals["mystring"] = "This is a C# string"; pe.Execute("print mystring", mod); pe.Execute("mystring = 'This is a Python string'", mod); Console.WriteLine(mod.Globals["mystring"]); //Expose a host method to IronPython and vice versa HostAPI myapi = new HostAPI(); mod.Globals["api"] = myapi; pe.Execute("api.Method()", mod); pe.Execute(@"def func(): print 'This is a Python function' ", mod); pe.Evaluate("func()", mod); //Use delegates over the same two methods HostAPI.Del hostdel = new HostAPI.Del(myapi.Method); mod.Globals["hdel"] = hostdel; pe.Execute("hdel()", mod); HostAPI.Del ipydel = pe.CreateMethod<HostAPI.Del>("func()", mod); ipydel(); } } public class HostAPI { public delegate void Del(); public void Method() { Console.WriteLine("This is a host method"); } } }
Now let's dig a little deeper. We've used only two types from the hosting APIs: PythonEngine, and EngineModule.
PythonEngine pe = new PythonEngine(); EngineModule mod = pe.CreateModule("mymod", true);
The PythonEngine is the primary entry point for code execution. The EngineModule is a scope in which code gets executed. PythonEngine exposes a DefaultModule, which you can use instead of creating your own EngineModule. You will notice that PythonEngine.Execute and PythonEngine.Evaluate methods have overloads that do not take an EngineModule. These overloads work against the DefaultModule.
EngineModule exposes a dictionary of defined names via its Globals property, which both host and scripts have read/write access to. The host is free to insert any .NET type into the dictionary, which then exposes that type to IronPython script code.
mod.Globals["api"] = myapi;
The last major concept here is the usage of delegates. .NET delegates are directly callable from IronPython. No special magic is required on either the host or script's part.
HostAPI.Del hostdel = new HostAPI.Del(myapi.Method); mod.Globals["hdel"] = hostdel; pe.Execute("hdel()", mod);
In the reverse, you can get a delegate to IronPython code in 1.1 via the PythonEngine.CreateMethod APIs. Sometimes this is more convenient than calling PythonEngine.Evaluate or PythonEngine.Execute.
HostAPI.Del ipydel = pe.CreateMethod<HostAPI.Del>("func()", mod); ipydel();
In my next entry I'll take this same example, re-write it for IronPython 2.0 Alpha 6, and discuss how and why things changed.
In the meantime, if you'd like to read more about hosting IronPython, the Cookbook has some good examples. And if there are any hosting concepts I haven't touched on that you wish I would, please let me know.