Ruby on MEF: Hybrid Application
Since the last installment in this little series, I've started to consider how Ruby/C# hybrid MEF applications might look.
The result is yet another component-based calculator:
Besides the Radiohead arithmetic, there is one reason to get excited...
Ruby parts! (I bet you hadn't guessed.)
The Ruby-based export and import features are heading towards a fairly natural syntax. IOperation, for example, is a regular .NET interface type:
Let's take a look at how the Ruby implementation is woven into the app.
This is still a work-in-progress, so configuration is basic. All of the Ruby parts are loaded from a single file that is fed into the RubyCatalog:
The calculator_ops.rb file contains part definitions like Multiply from above.
An additional catalog adds all of the C# parts to the composition as well:
The main window is a typical WPF Window that Imports a list of operations:
Because the MainWindow is instantiated and composed by MEF, all known exporters of IOperation will be provided, regardless of the language they're implemented in.
You'll notice that the MainWindow class implements the IErrorLog contract. This allows messages from the parts to be presented to the user:
Parts that wish to access the IErrorLog service from Ruby can import it:
The integration (and IronRuby in general) treats interface contracts as Ruby Module objects, so the IErrorLog used by the Divide part could be implemented by a Ruby object, although I haven't tested that case.
Fanatics take note: I did attempt to use IronRuby's case-transforming features to allow Symbol and Apply() to be specified in their natural Ruby forms (symbol and apply() ) but had no success. Hopefully I'll resolve this in a future version.
Monkeypatching the Export Type
I can't say whether this will turn out to be a good move or not, but right now it seems reasonable.
Notice this method call:
In order to support MEF's lazy-instantiation feature, the @error_log attribute needs to be of type System::ComponentModel::Composition::Export, which will supply the actual instance when it is required through the get_exported_object() method. Calling @error_log.get_exported_object.add_message felt decidedly unnatural, so I've added method forwarding to the Ruby version of the Export class:
I've had to do some type aliasing to disambiguate Export from Export<T> , but otherwise this is straightforward. Rubyists, please weigh in and let me know if this implementation is less-than-desirable :)
Once again you can download the full source code for this article from SkyDrive.