After writing the post Hosting IronPython in a C# application I decided to see how easy it was to host IronRuby in place of IronPython.
Before we go any further let’s setup a project. Create a console application (mine’s just named ConsoleApplication) and then, using NuGet, download/reference IronRuby. The version I have is 1.1.3.
Allowing our scripts to use our assembly types
Let’s look at some code
public class Application { public string Name { get { return "MyApp"; } } } class Program { static void Main(string[] args) { string code = "app = ConsoleApplication::Application.new\n" + "puts app.Name"; ScriptEngine engine = Ruby.CreateEngine(); ScriptScope scope = engine.CreateScope(); engine.Execute("require 'mscorlib'", scope); engine.Execute("require 'ConsoleApplication'", scope); ScriptSource source = engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements); source.Execute(scope); } }
Here, as per the IronPython post, we create some scripting code, obviously this time in Ruby. Next we create an instance of the Ruby engine and create the scope object. We then execute a couple of commands to “require” a couple of assemblies, including our ConsoleApplication assembly. The next two lines will also be familiar if you’ve looked at the IronPython post, the first of the two creates a ScriptSource object from the supplied Ruby code and the next line executes the code.
All looks good, except I was getting the following exception
An unhandled exception of type ‘System.MissingMethodException’ occurred in Microsoft.Dynamic.dll
Additional information: Method not found: ‘Microsoft.Scripting.Actions.Calls.OverloadInfo[] Microsoft.Scripting.Actions.Calls.ReflectionOverloadInfo.CreateArray(System.Reflection.MemberInfo[])’.
I found the following post IronRuby and the Dreaded Method not found error which demonstrated a solution to the problem.
If we insert the following line of code, after the last engine.Execute line
engine.Execute("class System::Object\n\tdef initialize\n\tend\nend", scope);
or in Ruby code we could have written
class System::Object def initialize end end
this will solve the problem – I don’t have any explanation for this, at this time, but it does work.
What if we already have an instance of an object in our hosting application that we want to make available to IronRuby ?
As per the IronPython example, we can simply set a variable up on the scope object and access the variable from our script, as per
string code = "puts host.Name"; ScriptEngine engine = Ruby.CreateEngine(); ScriptScope scope = engine.CreateScope(); scope.SetVariable("host", new Application()); engine.Execute("require 'mscorlib'", scope); engine.Execute("require 'ConsoleApplication1'", scope); ScriptSource source = engine.CreateScriptSourceFromString(code, SourceCodeKind.Statements); source.Execute(scope);
Using a dynamic to create our variables
Even cooler than creating the variables using SetVariable on the scope object, if we change our scope code to look like the following
dynamic scope = engine.CreateScope();
we can add the variables directly to the scope dynamic variable, for example
scope.host = new Application();