IronPythonをC#アプリに組み込む

最近Pythonがにわかにマイブームで、お仕事でもSQLiteデータベースのメンテやテストデータの生成にPythonを使ったりしています。そんな中、IronPythonC#アプリ(デスクトップアプリ)に組み込むのを試してみたら思った以上に簡単に出来てほええーとなったのでブログにしたためておいた次第です。

準備

IronPythonのランタイムをプロジェクトに追加しておきます。NuGetからインストールするのが一番楽です。

スクリプトエンジンとスクリプトスコープの作成
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
ScriptScope scope = engine.CreateScope();

スクリプトエンジン (Microsoft.Scripting.Hosting.ScriptEngine) はスクリプトを実行するクラスです。

スクリプトスコープ (Microsoft.Scripting.Hosting.ScriptScope) は変数やクラスの定義情報を格納するクラスです。エンジンにスコープオブジェクトを与えるとスクリプトはそのスコープの中で実行されます。IronPythonで定義した変数やクラスには場合はスコープオブジェクトからアクセスすることができます。

一行ずつ実行
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
ScriptScope scope = engine.CreateScope();
engine.Execute("print 'hello, world');
engine.Execute("foo = 100", scope);
engine.Execute("print foo", scope); // '100'と出力
int bar = engine.Execute<int>("foo", scope); // bar = 100

最後の行のように、式を評価した値を取得することもできます。

コンパイル + 実行
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
ScriptSource source = engine.CreateScriptSourceFromString(@"
def hello():
    print 'hello, world'
hello()
");
CompiledCode code = source.Compile();
try
{
    code.Execute(); // 'hello, world'と出力
}
catch (Exception exeption)
{
    System.Console.WriteLine("ランタイムエラー: " + exception.Message);
}

文法エラーなどはCompile()メソッドの引数に Microsoft.Scripting.Hosting.ErrorListener を与えることで取得できます。ランタイムエラーが発生した場合は例外がスローされます。

IronPythonからCLRクラスのメソッドを呼び出す
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
engine.Execute(@"
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import MessageBox
MessageBox.Show('hello, world')
"); // => System.Windows.Forms.MessageBoxを表示
C#からIronPythonの関数を呼び出す
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
ScriptScope scope = engine.CreateScope();
engine.Execute(@"
def hello():
    print 'hello, world'
", scope);
Action hello = engine.Operations.GetMember<Action>(scope, "hello");
hello(); // => 'hello, world'
C#からIronPythonのクラスのメソッドを呼び出す
ScriptEngine engine = IronPython.Hosting.Python.CreateEngine();
ScriptScope scope = engine.CreateScope();
engine.Execute(@"
class Foo:
    def bar(self):
        print 'Foo.bar()'
", scope);
object fooObject = engine.Operations.Invoke(scope.GetVariable("Foo"));
Action bar = engine.Operations.GetMember<Action>(fooObject, "hello");
bar(); // => 'Foo.bar()'