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
irovel for sale bortenat order veenat buy generic malegra dxt buy levitra vardenafil 20mg buy fluorouracil buy apcalis jelly 20mg buy cotrim online ketasma for sale buy duprost 0.5mg buy memantine 10mg amitryn 75mg buy sildigra xl plus online buy generic generic nuvigil buy snovitra online order generic femara malegra for sale x-vir entecavir baclof baclofen buy generic armod buy doxycycline online buy caberlin buy natamet buy viagra online buy diclofenac 50mg buy finasteride online tadalafil buy hostacycline online buy lioresal buy generic generic ampicillin buy generic cernos depot bupropion buy pelosta online buy megalis online admenta buy testosterone gel buy valif order xtane flagyl buy attentrol suhagra a-ret gel 0.1% buy sildigra online buy generic sirolimus online piracetam for sale fluconazole 200mg buy dilantin online ciplactin order generic imatinib buy caverta 100mg dapoxetine 60mg floricot 100mcg eriacta sildenafil citrate naltima order rifagut buy isotretinion online sildenafil citrate imatib buy zhewitra online rapacan sirolimus finpecia finasteride buy lenalidomide online buy generic esomeprazole buy erlonat 150mg avanafil for sale andriol testosterone undecanoate tadarise for sale anabrez 1mg sildenafil citrate buy sildenafil citrate buy vega tadalafil 20mg buy urimax online azithromycin buy forzest online tadaga 40mg buy prilosec premarin for sale sildenafil citrate buy malegra professional online buy generic flutivate cream super p force levetiracetam 750mg buy quetiapine online buy testosterone undecanoate online super kamagra buy buproprion viraday for sale arcalion sulbutiamine 200mg rogaine shampoo for sale flagyl 400mg prosteride buy oracea fluka for sale order quinine sulphate omnacortil buy generic mirtaz aldactone 25 mg buy fertyl super 100mg buy sulpitac order modafresh buy sildenafil citrate soft online buy avana buy mestilon silagra 100mg garlic himalaya for sale modalert buy calutide femalegra for sale cialis 20mg arpizol 5mg buy zantac 150mg buy tadalafil 20mg buy olanzapine online buy terbicip 250mg buy generic orlistat qutipin quetiapine buy generic amlodac buy sildenafil citrate jelly online buy rasalect vardenafil 20mg rizact 10mg buy cilostazol 100mg cenforce melanocyl methoxsalen order buspin buy malegra fxt 100/40mg normabrain piracetam armodafinil calaptin for sale soranib norvasc fertyl lasix for sale tadalafil 20mg lithosun sr for sale avagra order nizonide biduret amiloride buy tadalafil buy topirol online careprost bimatoprost opthalmic drops acamprol for sale buy vasotec 5mg buy generic sustanon buy lumigan 3ml tenofovir + emtricitabine buy tizan 2mg order ciprofloxacin tadarise pro for sale buy atorvastatin online buy kamagra oral jelly buy cutivate cream 0.05 % 90 gm nodict 50mg modvigil 200mg buy tenofovir aurogra generic gefitinib 250mg buy generic simvastatin buy armodafinil tadalafil super active for sale