IronRuby @ RubyConf 2009 – Part 3.5: Embedding IronRuby
Imagine you’re building a program to help create animations, visualizations, and other interactive applications. The requirements are simple:
- 2D rendering surface with simple primitive shapes
- Simple animation support – callbacks for each frame and each object on the canvas
- User-loadable “macros” for drawing and animating
A .NET developer can easily code up the first two requirements in C#, but implementing the third will be tricky. What does a macro look like? How do I discover them? How can I make it interactive? Why is this so hard!? This scenario requires the user to input some data, and the program must make an animation out of it; the data of your program is the code. Here are the options most .NET developers would come up with:
- Domain specific language – people usually cop out here and make it XML-based, which happens to produce the most human-unreadable code ever. This DSL could also be GUI-based, but you won’t get any programmers interesting in extending your app that way.
- Completely punt on the interactivity and require the macros be .NET DLLs.
- Find a way to use C# interactively - either using CodeDom to compile and run C# code dynamically (much like ASP.NET does), or code-generate a valid C# class from the snippet, compile it to a DLL (aka shell out to csc.exe), loading that DLL, and finally reflecting over that DLL to call the user’s code. This is, of course, ignoring the question of whether C# is a good macro language or not. It’s worth noting that Mono supports hosting it’s C# compiler, so you could do more dynamic things with C# through Mono.
Given all that talk about Ruby before, let’s try using IronRuby to write these macros. Here’s a C# app to start from:
The starter app does ABSOLUTELY NOTHING; just a Window with a Canvas and a bunch of textboxes for coding. Keep in mind this is just a demonstration, and this app could have been written entirely in Ruby, but the point is to show .NET developers how powerful an embedded scripting language can be.
Setting up your environment
.NET developers have choices for development environments: mainly Visual Studio or SharpDevelop, or even the command-line and text-editor (I left out MonoDevelop since Mono doesn’t support the Windows UI stuff I’m doing, but a future version will be able to run in Mono). I’ll be using Visual Studio 2010 Beta 2 for this walkthrough’s screenshots and examples, specifically because it C# 4 has special dynamic language features,
but you can also use C# 3 with Visual Studio 2008 ( free version here ), or just stick to a text-editor and MSBuild.
If you’re using .NET 4.0,
sketchscript\sketchscript.sln is the solution file you want to use.
The .NET 3.5 version is .
sketchscript\sketchscript3.5.sln, but seriously, try out .NET 4.0
I haven’t tested it out in VS2008 yet, so please bare with me. If you’re feeling adventurous, you can fork my git repo, get it working in VS2008, and I’ll pull your changes in.
Adding references to IronRuby
If you downloaded SketchScript from the above link, you’ll find four DLLs required for embedding IronRuby in the
sketchscript\ironruby directory. Add those as references to the sketchscript.csproj:
In case you’re curious about what each DLL is:
IronRuby.dll is the IronRuby compiler, while
IronRuby.Libraries.dll is the core libraries of Ruby.
Microsoft.Dynamic.dll are the APIs that IronRuby depend on for DLR compiler features, and
Microsoft.Scripting.dll is the DLR Hosting API.
If anyone who has used IronRuby before is thinking there are missing DLLs, then you’re right. IronRuby’s
Microsoft.Scripting.Core.dll has been integrated into .NET 4.0 as the new
System.Core.dll. This also removes the need for
Now let’s get that code window working; first add some
using statements at the top of MainWindow.xaml.cs:
And some fields to hold onto the scripting engine anywhere in the
Now initialize the scripting engine; add this code to the
Loaded event action, after setting the
OutputBuffer but before the
KeyBindings() call (around line 71):
And lastly let’s run the code when Ctrl-Enter is pressed. Since that’s already set up for us, all we need to do is add this code in the
RunCode method, right at the "TODO" comment around line 92:
And that’s it! Now you’ll be able to run some actual Ruby code:
Interacting with the host application
Though Ruby code can run, there is no obvious interaction with the host application. That black void of a canvas on the left would be completely useless if it wasn’t accessible from Ruby code, so add this single line to the
Loaded event action, before the
Note: if the host application didn’t do this, it would still be possible to get to the canvas from Ruby code, but the script writer would have to do it themselves:
So, as a general rule-of-thumb, have your host program decide what parts to extend to script code, and have the script code only use that object-model, though their may be ways around it.
Trying it out
Now that there’s a way to draw on the canvas, play around with drawing random things on the screen. Here’s a little script I’ve been playing with:
Which draws this:
Ooo, pretty! So without much effort we have a very extendable application. Before you get carried away playing around with making pretty things, there’s one more thing to do to make this app really awesome …
While animations could still be built with IronRuby’s native thread support or WPF animations directly from Ruby code, it’d be nice for the host to provide some simple animation support, like a callback that fires for every frame, and even a way to attach animations to any object.
The host currently supports these two callbacks, but they need to be wired up. Add the following code at the bottom of the
Loaded event action, before the call to
And also add this call at the end of the
Lastly we need to implement
CatureAnimationCallbacks, by looking for special method names to get a hold of. Look for
each_frame as the
EachFrame action, and
each_object as the
Now 30-times-a-second the host will try to call
each_frame, and it will call
each_object once for each element on the canvas, store the return value on the actual element, and then try to call an
update method on that stored object 30-times-a-second. This lets you either run random animations, or specific behavior for objects.
A good animation example is bouncing, so let's run the script that produced all the squares first, and then run the following code to make them all bounce.
And now it should look something like this:
There are more goodies to run in the features directory, but try writing your own fun little animations. Jim Deville ported the tutorial from Jeff Casimir’s Code of Art talk, which is pretty fun to play with, so if you make your own please post a comment with a screenshot and code!
And there you have it, IronRuby embedded to do interesting things. In case you missed any steps along the way, here's the full diff against the initial download.
Next stop, IronRuby’s status and roadmap.