C#からIronPython2.6を呼び出す方法

IronPython2.6RC3が出ているというのに、C#からIronPythonを呼び出して使う方法についてはIronPython1.1の情報だったり2.0系列が多いのでちょいとメモを残しておきます。
IronPython自体のマニュアルに載せてくれればいいんですが、チュートリアルとかには載ってないみたいですね。おそらくDLRをホスティングするもう一段抽象化したところを探しに行けば見つかりそうな気がしますが後で*1

とりあえず確認したのは以下のようなこと。

  • C#からIronPythonのコードを動的に実行
  • IronPythonで使いたいアセンブリへの参照をC#で追加する
  • C#のアセンブリ内で定義したinterfaceを実装する(継承する)
  • IronPythonの変数をC#で受け取る・セットする
  • IronPythonの関数をC#から呼び出して返り値を取得する
  • IronPythonで定義したクラスをC#で実体化する

まずはVisual Studio*2を起動してC#の「コンソールアプリケーション」のプロジェクトを一つ作ります。(もちろん「Windowsフォームアプリケーション」でもOKです)
次に、上記URLからIronPython-2.6-Bin.zipをダウンロードしましょう。中にいくつかのdllが入っているのを確認したら、先ほどのプロジェクトに必要な参照を追加しましょう。
以下のdllの参照を追加します。

  • IronPython.dll
  • Microsoft.Scripting.dll
  • Microsoft.Scripting.Core.dll

こんな感じです。

これで実行しようとすると、dllが足りねーぞと怒られるので、次のdllも実行ファイルができるフォルダに入れておきます。

  • IronPython.Modules.dll
  • Microsoft.Dynamic.dll
  • Microsoft.Scripting.ExtensionAttribute.dll

使われないMicrosoft.Scripting.Debugging.dllというのがありますが、普通に実行する分には無くても支障ありません*3

でもって後はコードを書くだけです。

IronPython側 (Scripts/Sample.py)

# -*- coding: utf-8 -*-
import clr
# C#のアセンブリを参照している
# 本来必要なclr.addReferenceの処理はC#側で
# 先にやってしまってるので普通にimportするだけで良い
from IronTest import *

print 'Sample.py Called'

# C#でセットした変数を表示する
print message_from_csharp

# C#側で参照される変数
message_from_python = 'Hello IronPython'

def test(num):
  print "test func called: value %d" % num
  return "test result"

# C#側で定義したインターフェースを実装する
class TestPlugin(IPluginBase):
  def install(self):
    print "install called"

C#

using System.Reflection;
using System;
using System.IO;
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;

namespace IronTest
{
 public interface IPluginBase
 {
  void install();
 }

 class Program
 {
  static void Main(string[] args)
  {
   // スクリプトファイルのパスを求める
   Assembly assembly = typeof(Program).Assembly;
   string dirPath = Path.GetDirectoryName(assembly.Location);
   string scriptPath = Path.Combine(dirPath, "Scripts");
   string filePath = Path.Combine(scriptPath, "Sample.py");

   // Python スクリプトエンジン生成
   ScriptEngine engine = Python.CreateEngine();

   // このアセンブリをPython側で参照できるようにしておく
   // もちろん別のアセンブリにしてそれを参照するようにもできる
   ScriptRuntime runtime = engine.Runtime;
   runtime.LoadAssembly(Assembly.LoadFile(assembly.Location));

   // Python スクリプトを読み込む
   ScriptSource source = engine.CreateScriptSourceFromFile(filePath);
   CompiledCode code = source.Compile();

   // スコープ生成
   // IronPython に変数を渡したり取得したりするときに使う
   ScriptScope scope = engine.CreateScope();

   // Python側のスコープに変数をセットする
   scope.SetVariable("message_from_csharp", "Hello CSharp");

   // Python スクリプト実行(関数定義、クラス定義が完了する)
   code.Execute(scope);

   // スクリプトのスコープ内で生成された変数を取得
   Console.WriteLine(scope.GetVariable<string>("message_from_python"));

   // Python側で定義した関数を呼び出す
   Console.WriteLine(engine.Execute("test(100)", scope));

   // Python側で定義したクラスをインスタンス化
   IPluginBase plugin = (IPluginBase)engine.Execute("TestPlugin()", scope);
   plugin.install();

   // enterが押されるまで待機
   Console.ReadLine();
  }
 }
}

実行結果

Sample.py Called
Hello CSharp
Hello IronPython
test func called: value 100
test result
install called

関数呼び出しはengine.ExecuteによるPythonコードの実行なので、オブジェクトを引数に渡すのは難しそうですね。基本的な使い方としては2.0系列と変わってないみたいです。

久しぶりに書いたらはてなのスーパープレ記法の書き方を忘れてた(汗)

*1:無ければIronPython自体のソースを読みましょう

*2:無い人は無償のVisual C# 2008 Express Editionを落としましょう

*3:デバッグ用の関数とかが入ってる気がしますが、リファレンスが見つからないのでわかりません