Windows Phone

Use IronRuby to develop a Windows Phone 7 app

After Justin James got his feet wet with IronRuby, he was eager to use it in a real-world scenario. Find out what he thinks of using IronRuby to write a WP7 app.

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.

Note: This tip first published in TechRepublic's Smartphones blog in February 2011.

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.

0 comments

Editor's Picks