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.
Configuration
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:
Main Window
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.
Bi-Directional Composition
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 :)
Source Code
Once again you can download the full source code for this article from SkyDrive.