Windows Phone

Use IronRuby to develop a Windows Phone 7 app

Justin James recently got his feet wet with IronRuby, and he was eager to use it in a real-world scenario. Find out what this Ruby novice thinks of using IronRuby to write a Windows Phone 7 application.

A Windows Phone 7 application I was working on kept sitting on the shelf because I was too overwhelmed with a couple of contracts to finish it. Then several weeks ago I won a free Samsung Focus in a Microsoft-sponsored contest, and the application (currently called Lifting Calculator) went from a hobby to a valuable tool. It also seemed like the perfect opportunity for me to try out IronRuby in the Windows Phone 7 environment.

Download IronRuby

I had IronRuby 1.1.1 installed, but I learned that IronRuby 1.1.2 was released in early February, and it includes Windows Phone 7 support. You need to download the binaries package (the MSI version is not fine -- I learned that the hard way). Also, while you are in the directory, look at each DLL's properties and click the Unblock button.

Write a Windows Phone 7 app in IronRuby

I read Shay Friedman's MSDN article about writing Windows Phone 7 apps in IronRuby, and then I tweaked his directions to fit my needs. One reason for the tweaks is that I already completed quite a bit of work on my application, and I wanted to see how to use IronRuby for one piece of my app first. Another issue was that the article's instructions were not entirely correct; I'm sure that this was primarily due to IronRuby changing since the article was written.

Here are the steps that I took:

  1. I added a reference to the IronRuby binaries. I added all of the Windows Phone 7 binaries from the package.
  2. I prepped my XAML and attached code behind for the functionality I wanted. (No, I haven't moved to MVVM yet.)
  3. In the C# code that I wanted to call Ruby from, I added the following using statements:

    using System.Reflection;

    using System.IO;

    using Microsoft.Scripting.Hosting;

    using IronRuby;
  4. I created my Ruby file that contained the functionality I wanted. The Ruby script should manipulate global variables that will be passed into it by the Silverlight host, and return an output value.
  5. I set the Build Action and Copy to Output Directory of the new Ruby file as Content and Copy if newer, respectively.
  6. I added the code needed to call the IronRuby script, and received its output into a variable. I had to cast it to make sure I could use it nicely after I got it back.

For the full details, see Code sample A (the C# code calling the IronRuby script) and Code sample B (the IronRuby script). To get an idea of what this app does, this functionality is to determine how many weight plates of what size to put onto each side of a barbell to load it to the desired weight. This is a big help in the gym once you get past a certain point, and you end up taking forever to do the math and end up improperly loading the barbell. (This is part of a bigger project that I am working on, and I hope to release it in the near future.)

In Shay's article, he passes in the entire application to the script; for my discrete functionality, that is overkill. Instead, I pass in an object that represents my input parameters and an object that will hold my output. After I run the script, I use the output information to put the results on the screen. If I really wanted to be picky, I could bind the controls to this object.

Code sample A: The C# code to call the IronRuby script and consume its output.
private void ShowBarbellLoadout(int barbellWeight, int desiredLoad)
{
var resourceStream = Application.GetResourceStream(new Uri("BarbellLoader.rb", UriKind.Relative));
var dataFile = new StreamReader(resourceStream.Stream);
var code = dataFile.ReadToEnd();
var engine = Ruby.CreateEngine();
engine.Runtime.Globals.SetVariable("BarbellWeight", barbellWeight);
engine.Runtime.Globals.SetVariable("DesiredLoad", desiredLoad);
var loadoutResults = (IronRuby.Builtins.Hash)engine.Execute(code);
var results = new List<BarbellLoadout>
{
{new BarbellLoadout{ PlateSize = 45, PlateCount = int.Parse(loadoutResults["45"].ToString()) }},
{new BarbellLoadout{ PlateSize = 25, PlateCount = int.Parse(loadoutResults["25"].ToString()) }},
{new BarbellLoadout{ PlateSize = 10, PlateCount = int.Parse(loadoutResults["10"].ToString()) }},
{new BarbellLoadout{ PlateSize = 5, PlateCount = int.Parse(loadoutResults["5"].ToString()) }},
{new BarbellLoadout{ PlateSize = 2.5M, PlateCount = int.Parse(loadoutResults["2.5"].ToString()) }}
};
loadingChart.ItemsSource = results;

mainPivotControl.SelectedItem = barbellLoading;

}
Code sample B: The IronRuby script.
currentTotal = DesiredLoad.to_i - BarbellWeight.to_i
output = {}

output["45"] = (currentTotal / 90).truncate

currentTotal -= output["45"] * 90

output["25"] = (currentTotal / 50).truncate

currentTotal -= output["25"] * 50
output["10"] = (currentTotal / 20).truncate
currentTotal -= output["10"] * 20
output["5"] = (currentTotal / 10).truncate
currentTotal -= output["5"] * 10
output["2.5"] = (currentTotal / 5).truncate
currentTotal -= output["2.5"] * 5
return output

I learned a very important lesson: All global variables set from the outside must have a capitalized variable name; otherwise, the variables will not work. Even more frustrating, in my script I prefaced the global variables with a dollar sign, and they didn't balk -- the variables auto initialized to the defaults, giving me no output. It took me a couple of hours to really get things working.

One alternative to my technique is to use the dynamic functions of C# 4. For example, a Ruby script could create classes, and you could use dynamic to instantiate them and run their methods from C#. I didn't try this directly, but dynamic worked when I gave it a dry run in plain C#, and I have no reason to believe that it wouldn't work to call IronRuby code.

Conclusion

This novel approach is not terribly practical, unless you prefer Ruby or have a deep investment in it. The big issue is that the already lean debugging options are nonexistent in the hosted DLR scenario. You can't step into the Ruby code; for me to debug it, I copied it into another IronRuby project and set up global variables to replicate what my C# code was setting so I could step into the code. This might be enough for a Ruby pro, but as a Ruby novice, it's not what I need. All the same, using IronRuby in Windows Phone 7 apps is of interest.

If you want more Ruby on Windows Phone 7, check out iron7, a Ruby interpreter for WP7.

J.Ja

About

Justin James is the Lead Architect for Conigent.

4 comments
iron 7
iron 7

Thanks for mentioning iron7 - having lots of fun developing scripts with it. I'm also now using Ruby in a few other apps, especially trying to use it for writing apps which support plugins. The source for iron7 is now available on github and if anyone wants to help support further development then they can do so by leaving positive comments on http://www.wp7comp.com/iron7-ruby-scripting-on-windows-phone-7/trackback/ - will maybe help us win $10000 to pay for more development. Thanks again for the mention!

Justin James
Justin James

I was all set to enter that contest, before they extended the deadline, I just needed to do the screencast. There were less than 20 entrants, so there was a good shot at winning. No way would I go up against iron7, though... much better than my "Name that Nerd" game! J.Ja

Justin James
Justin James

Are you in love enough with both Ruby and WP7 to go through the extra work to use Ruby on WP7? J.Ja

iron 7
iron 7

I don't think its suitable for all apps. However, where it is useful is if you need to extend an app after publishing it - e.g. consider the case of a game with extra levels or with extra AI that you can download in Ruby format after purchasing the game, or consider a photo editing app where you can download Ruby filters from a web server. And both of these options could require micro-payments - which seems to be the growth area for app revenue at present.