IronRubyによる.NETとRubyの相互運用

今までのソフトウェア開発の経験の中でカスタマイズ要素や機器制御における機器構成のバリエーションあるいは開発で使用する各種シミュレータやデバッグツールなどで、振る舞いを変更できるように設定ファイルや変な外部DSLの様なものを作ったりして対応してきました。
そんな時にも長年の間スクリプト言語の相互運用ができればと思ってWSH等にも期待していたのですがどうも決め手に欠いていました。
そんな中でIronRubyの1.0版がリリースされたということで試してみました。
結論を一言で言えばこれならば業務で色々と活用できて、さらに設計に活用できそうだと思いました。
ぱっと思いつくところでは

  • DIコンテナの設定の代わりにインスタンスの生成とプロパティ設定
  • カスタマイズ可能なインスタンスビルダー
  • 複数の項目に整合性などの依存関係がある設定
  • 通信プログラムのデバッグ、シミュレーションツールのプロトコルカスタマイズ記述
  • デバッグ作業のサポートツールとして

などで使えそうです。
設計への導入についてちょっと弱気なのは要件やクライアントの意向などが懸念事項なのですがもし制約さえなければ使わない手はないでしょう、という感じです。
IronRubyに関する情報ではRubyから.NETオブジェクトを使用する例は結構紹介されていますので、ここでは.NETソフトウェアからRubyスクリプトを使用する方法を中心に紹介しておきます。

.NET Framework

以下のサンプルでは.NET 4と.NET 4向けパッケージの組み合わせと、.NET 3.5 SP1と.NET 2向けパッケージの組み合わせのそれぞれで確認しています。
.NET 4で開発する場合にはVisual Studio 2010が必要になります。
.NET 3.5 SP1の場合にはVisual Studio 2008を使用しました。
それぞれの開発環境に応じて対応する.NET Frameworkがインストールされているはずですが必要であれば「.NET」このページからダウンロードします。

IronRubyのダウンロード

IronRubyは「http://ironruby.net/Download」からダウンロードできます。
.NET Framework 4向けと.NET Framework 2(以降)向けのパッケージがあります。
ちょっと試した限りでは例えばIronRubyの「http://ironruby.net/Documentation/.NET/Hosting」このページのサンプルなどの.NET 4向けのコードで.NET 2向けパッケージで動作しないことがありましたが以下のサンプルコードについては両方のパッケージで同様に動作します。
IronRubyのインストールはインストーラーにしたがってすべてデフォルトでインストールしました。

Visual Studioプロジェクト

下記のサンプルではコンソールへの出力を行いますのでVisula C#のコンソールアプリケーションのプロジェクトテンプレートを使用してプロジェクトを作成してください。
それから参照設定の追加でプロジェクトにIronRubyのbinフォルダ(例えば"C:\Program Files\IronRuby 1.0\bin")にある以下のDLLの参照を追加してください。

この他にも依存するDLLがありますがビルドの際に実行フォルダに一緒にコピーされます。

サンプルソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IronRubyTest
{
    public class Bridge
    {
        public static event Action<string, object> EventChange;
        public static Func<int, int> DelegateJob;

        public static void CallEventChange(string name, object value)
        {
            EventChange(name, value);
        }
    }

    class Program
    {

        static void Main(string[] args)
        {
            var runtime = IronRuby.Ruby.CreateRuntime();
            var engine = runtime.GetEngine("Ruby");

            // runtime.ExecuteFile(@"..\..\ruby_script.rb");  // ruby_script.rbファイルをloadする場合
            engine.Execute(
@"
# C#のプログラムから呼ばれるメソッド
# Stringを返すメソッド
def ruby_method
  puts 'Vow Wow Wow!'
  'nice!'
end

# RubyのHashオブジェクトを返すメソッド
def get_ruby_hash
  {:name=>'Jamzz', :age=>42}
end


# 以下でRubyからC#のプログラムを呼び出す
# このプログラム(Assembly)をrequireする
require 'IronRubyTest'
# .NET Framework 3.5のSystem::Funcを使用するためにrequireする
require 'System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'

# EventにRubyのブロックを登録する
IronRubyTest::Bridge.EventChange {|name, value| puts 'The ' + name + ' is changed!\nThe value is ' + value.to_s + '.'}

# Rubyのブロックを含むDelegateをセットする
IronRubyTest::Bridge.DelegateJob = System::Func[System::Int32, System::Int32].new {|num| num * 2 + 1}
");

            // Rubyのメソッドを呼び出す。戻りは"nice!"
            var ret = engine.Execute("ruby_method");

            // Rubyのメソッドを呼び出してRubyのHashオブジェクトを受け取る
            ret = engine.Execute("get_ruby_hash");
            // RubyのHashオブジェクトはDictionary<object,object>を継承している
            foreach (KeyValuePair<object, object> item in ((Dictionary<object, object>)ret))
            {
                Console.WriteLine(item.Key.ToString() + " => " + item.Value.ToString());
            }

            // C#のEvent経由でRubyスクリプトを呼び出す
            Bridge.CallEventChange("Count", 4);

            // C#のDelegate経由でRubyスクリプトを呼び出す
            ret = Bridge.DelegateJob(3);
            Console.WriteLine("The job result is " + ret.ToString() + ".");
        }
    }
}

簡単にだけ補足するとIronRuby.Ruby.CreateRuntime()でruntimeを生成します。
このruntimeがRuby実行環境のインスタンスになります。
runtime.GetEngine("Ruby")でengineを取得してengine.Execute()メソッドでこのruntime上でRubyを実行します。
上記の例ではサンプルとして試行錯誤しやすいようにRubyスクリプトC#プログラム内に記述していますが通常はこの様なスクリプトは外部のテキストファイルに記述されると思います。
例えば上記のスクリプトを"ruby_script.rb"というファイルに保存してruntime上でこのRubyスクリプトファイルを実行する場合、つまりRubyで"load 'ruby_script.rb"を行うのと同じことを行う場合にはruntime.ExecuteFile("ruby_script.rb")メソッドを使用します。
これらのメソッドの引数や型についての詳細についてはVisual Studioのオブジェクトブラウザでも確認してみてください。