the blog
Back to -Blog

From C# to Ruby (and back again) - The Ruby Connector

How To Get .NET and Ruby To Talk To Each Other
by Huw Collingbourne
Wednesday 4 July 2007.

Having talked a bit about the Ruby Connector in previous Blog posts (that’s the widget that will let your .NET programs run simultaneously with your Ruby programs in the version 1.2 of Ruby In Steel), I think the time has come to put a bit of flesh on the bones. In this post, I’ll try to give you a quick guide to how the Ruby Connector is used in practice.

The Ruby Connector can make sense of even quite complex data structures sent from Ruby to .NET. Here, for example, I am debugging a C# program which has just received from Ruby a Hash containing some nested arrays. Some methods of the Ruby Connector have parsed the hash into a series of data items, each of which contains information on the Ruby class, the length of the data, the data itself and its object_id...

This article is a preview of one of the features that will be released in Ruby In Steel Developer 1.2

When we began work on the Ruby Connector, the lines of communication which it opened up between .NET and Ruby were fraught with hazards. The problem is that .NET languages such as C# know nothing about the vagaries of Ruby; and Ruby, for its part, knows just as little about the ways of .NET. So, while it’s easy enough to send a message from C# to Ruby, there is no guarantee that Ruby will know what it means when it receives it – and, even if it does, it won’t have any way of sending back a sensible reply.

One of my aims has been to make it possible for C# or VB .NET to communicate easily with Ruby. Sending messages to Ruby should, in principle, be just like calling functions in C#. And when Ruby wants to send back a message it should just be able to write some output and not have to worry about what happens to it next...

All of which is much easier said than done!

Cracking The Code

Let’s consider a very simple example. Let’s suppose that the message you send (from C# to Ruby) is 1+2. Ruby should (we hope!) reply 3. Of course, in reality, the messages you send could be much more complex – maybe you want Ruby to load a database, sort an array or kill the Troll with the Golden Sword. A single integer reply, 3 (in response the message 1+2 is easy to deal with. But what if Ruby replies by sending back a Hash containing a mix of different object types such as Strings, Fixnums and Arrays (or Treasures, Weapons and Monsters, depending on the type of programs you write)...? When the C# side of the Connector receives this unpredictable stream of data it has to have some way of ‘decoding’ it.

We’ve addressed this problem by writing a small Ruby module which parses each piece of data from Ruby into four component parts – Class, Length, Data and Object ID. When the data is received at the .NET end of the Connector (by a C# or VB program, say), it can be reconstructed from this information – so a Ruby Array can be turned into a .NET Array, ArrayList or List, a Ruby Fixnum becomes a .NET integer and so on.

Programming the core functionality of the Ruby Connector was only the start, however. As I said earlier, from the outset we realised that this would be no good if it was hard to use. Ironically, making it easy has turned out to be one of the hardest things to achieve...

Making A Connection

Let me give you a concrete example of how you would actually use the Ruby Connector. First, as with any .NET control, you’d drop the Connector onto a form in the design workspace where (being a ‘non-visual’ component) it would dock itself down at the bottom.

Here the Ruby Connector appears under the form designer. In the final version, its icon may look a bit more Ruby-like...

You would then set a few properties using the Properties panel. The two critical properties are the path to the Ruby interpreter and the path to the Ruby program that you want to run.

Now you have to tell the Connector to start Ruby. Assuming you’ve called the connector rc, this is how you’d start it:

rc.Start();

For the sake of simplicity, we’ll ask Ruby to add 1 to 2. Here goes...

TypeAndReply tr = rc.TRSendMsgInputEval(“1+2”);

The Ruby Connector comes with built-in methods to send different ‘types’ of message to Ruby. Here I am sending the input “1+2” and asking Ruby to evaluate it. The TRSendMsgInputEval() method returns Ruby’s answer – this answer has two parts: the type of answer (for example, OUTPUT_REPLY or ERROR_REPLY) and the reply itself. The reply is a string that’s packed with all the information I mentioned earlier – each data item returned by Ruby provides its class, data, length and object id. A single reply may contain multiple data items – in which case the items are divided into lines (separated by “\r\n”). In the present case, this is the reply from Ruby:

"|Fixnum|1|3|7|\r\n"

Here Fixnum is the class, 1 is the ‘length’ in characters of the data, 3 is the data itself and 7 is the object ID. This is all useful stuff but not too convenient if you just want to get back the answer (3) and display it in a text box. To assist in this the reply supplied by a TypeAndReply structure can be transformed into a RubyDataItem, like this:

RubyDataItem item = new RubyDataItem(tr.reply);

The RubyDataItem contains a number of fields which will be initialized by Ruby’s reply. In the current case, here are some of the fields of the item object which we just created:

Item.className: "Fixnum"
Item.data: "3"       
Item.Length: 1
Item.objectID: "7"

If the class of the item happened to be an array you could now divide it up into its component parts (the Ruby Connector has methods to assist in this). So, in short, this is the entire C# code need to get Ruby to add 1 to 2 and display the result in a text box:

TypeAndReply tr = rc.TRSendMsgInputEval(“1+2”);
RubyDataItem item = new RubyDataItem(tr.reply);
textBox1.Text = item.data;

Or, if three lines of codes offends against your desire for brevity, you could cram it all into one line, like this:

textBox1.Text = rc.GetRubyDataItem(rc.TRSendMsgInputEval("1+2")).data;

Condensing it down in this way is all very well with such a simple message, but in general, it is safer to do the operation in two parts – first by constructing a TypeAndReply object, then initializing a RubyDataItem object. This is because each of these objects can provide lots of useful information; if Ruby doesn’t return the expected reply, the TypeAndReply object will show you which ‘type’ of reply was actually returned, plus the entire unprocessed string sent back by Ruby. The RubyDataItem, for its part, can supply information on the amount of data (the number of individual items in a Hash, say) and the class, length and object ID of each. This information can be a huge advantage when working with complex data structures or when trying to diagnose any errors returned by Ruby.

Incidentally, some of you may have noticed that I keep talking about “sending messages” to Ruby and “getting replies” in return. If this sounds a bit Smalltalk-ish, I have to confess that the resemblance is far from accidental.

When using the Ruby Connector, you are forced to work with a highly encapsulated system. Ruby really is like a ‘black box’ to which messages can be sent and from which answers are returned. The secret of getting this to work smoothly is getting Ruby and .NET to agree on the nature of the messages and replies each other is able to understand.

In The Raw...

Just in case you don’t want to go through the highly structured message-and-reply channels of communication which I’ve been describing here, the Connector also provides a channel for ‘raw’ input and output through which unprocessed, unchecked messages and replies can be sent and received. This may be useful in some circumstances, but it does put the onus of checking and ‘decrypting’ the messages on the programmer.

As a general rule, I would strongly recommend sticking to the ‘safe routes’ of communication which we’ve built into the Connector. Put it this way: when I started work on the Ruby Connector, the ‘raw’ channels were all that I had available to me – and my programs kept crashing! Now that we’ve defined more rigorous protocols, my programs very rarely crash - well, only about as often as any other programs I write crash ;-). Moreover, when an error occurs on the Ruby side of the Connector, the error message gets passed back to me on the C# side. Before we added that capability, it was quite possible to spend ages debugging C# code only to discover, much later on, that the problem was a missing bracket or semicolon in the Ruby code! Now, when that sort of error occurs, I can find out immediately – something that has saved me a few sleepless nights...

Ruby Says Hello To C#

Here’s a simple example of a message sent from C# and a reply returned by Ruby...
First I write a Ruby method to return “Hello world”...

...then I write some C# code to input and evaluate the message “hello” (in other words, to call Ruby’s ‘hello’ method...

On the form (which I’ve created in Visual Studio), I click the button to run my C# code...

And Ruby’s reply is displayed in the rich text box.

More on some of the things you will be able to do with the Ruby Connector in another Blog post soon...


p.s. I’ve said this before but, to avoid confusion, it may be worth saying again: the Ruby Connector is completely unrelated to the Visual Rails Workbench (another feature that will form part of Ruby In Steel 1.2). Dermot will be blogging more on that subject in a day or two...

Bookmark and Share   Keywords:  development
© SapphireSteel Software 2013
buy cheapest flonida in canada buy dapoxetine australia http://uic.asso.fr/diomis/spip.php?buy=173231 http://uic.asso.fr/diomis/spip.php?buy=748716 about rogaine shampoo and conditioner budeprion xl 300 bupropion hcl oral cernos gel sun pharma http://uic.asso.fr/diomis/spip.php?buy=651367 information about buy zhewitra without a prescription a generic for lipitor purchase metronidazole nexium 40mg fluticasone propinate in california rapamune axid omeprazole pills purchase vilitra buy enalapril maleate 10 mg http://uic.asso.fr/diomis/spip.php?buy=102101 http://uic.asso.fr/diomis/spip.php?buy=676527 buy a-ret without a prescription doxycycline 100 in mexico buy wellbutrin online australia in united states flagyl online cutivate cream 0.05 bactrim suspencion http://uic.asso.fr/diomis/spip.php?buy=5834 http://uic.asso.fr/diomis/spip.php?buy=649892 diflucan 60 mg http://uic.asso.fr/diomis/spip.php?buy=151302 can you quinine sulphate buy levipil canada bupropion 24 xl tab 300mg http://uic.asso.fr/diomis/spip.php?buy=953355 http://uic.asso.fr/diomis/spip.php?buy=805482 buy generic nodict letrozole bodybuilding in uk buy topirol without a prescription http://uic.asso.fr/diomis/spip.php?buy=593272 lenalidomide cost femara generic canada drugs costs for revlimid http://uic.asso.fr/diomis/spip.php?buy=539197 orlistat msds voltaren buy http://uic.asso.fr/diomis/spip.php?buy=138122 bactrim ds bid in canada karachi sustanon generic zocor cost lasix furosemide rifaximin cost http://uic.asso.fr/diomis/spip.php?buy=391002 about cilostazol 50mg tablets canadian lipitor information about buy azithromycin uk buying piracetam michigan in california doxycycline 50 mg clomiphene citrate cost