[C#][dynamic] プラグイン処理 2 (DLL/C#/Python に対応させてみる)

Dynamic

前回の「プラグイン処理」の続き。

今回は、前回のコードに少し付け足して、様々な種類のプラグインに対応してみよう。

前回は、DLL だけをプラグインとして使えるようにしたが、今回は、それに加えて、C# と Python のプラグインも使えるようにしてみたい。

■ 今回のプラグインの規約

今回のプラグインも、前回同様、以下のような規約ベースで動くものとする。

  • 実行中のプログラムがあるフォルダーの下の Plugin と云う名前のフォルダーにある dll ファイルをプラグインのアセンブリ、cs ファイルを C# のプラグイン、py ファイルを Python のプラグインと看做す。
  • DLL プラグインと C# プラグインでは、最初の public なクラスをプラグインと見做す。
  • プラグインは、必ず string Name() と云う名称を返す public なメソッドを持っている。
  • プラグインは、必ず void Run() と云う public なメソッドによって動作する。

■ プラグイン側の実装の例

では、プラグイン側から実装してみよう。

今回用意するのは、以下の三種類だ。

・DLL プラグインの実装の例

DLL プラグインは、クラスライブラリとして作成し、ビルドして "Test.dll" とする。

public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。

// DLL プラグイン (クラスライブラリとして作成し、ビルドして "Test.dll" に)
using System;
public class Plugin
{
public string Name()
{
return "DLL Plugin";
}
public void Run()
{
Console.WriteLine("DLL Plugin is running!");
}
}
・C# プラグインの実装の例

C# プラグインは、一つの cs ファイルだ。"Test.cs" として保存する。

public なクラスを持ち、その中には、public な Name() メソッドと public な Run() メソッドを持つ。

// C# プラグイン ("Test.cs" として保存)
using System;
public class Plugin
{
public string Name()
{
return "C# Plugin";
}
public void Run()
{
Console.WriteLine("C# Plugin is running!");
}
}
・Python プラグインの実装の例

Python プラグインは、一つの py ファイルだ。"Test.py" として保存する。

Name() メソッドと Run() メソッドを持つ。

# Python Plugnin (Save as "Test.py".)
def Name():
return "Python plugin"
def Run():
print "Python plugin is running!\n"

■ プラグインが組み込まれる本体側の実装の例

次に、プラグインが組み込まれる本体側の実装だ。

・IronPython を利用する為の準備

先ず、Python をプラグインとして使えるようにするために、IronPython をインストールしよう。

IronPython は、Visual Studio で NuGet からインストール出来る。

IronPython のインストール
IronPython のインストール

IronPython のインストールが終わると、プロジェクトの参照設定は、次のように IronPython を使う為の参照が追加されている。

IronPython のインストール後の参照設定
IronPython のインストール後の参照設定
・プラグインが組み込まれる本体側の実装の例

では、本体側を実装しよう。

using IronPython.Hosting; // Python プラグインの処理に必要
using Microsoft.CSharp; // C# プラグインの処理に必要
using System;
using System.CodeDom.Compiler; // C# プラグインの処理に必要
using System.IO;
using System.Linq;
using System.Reflection;
class Program
{
// プラグインのフォルダー名
const string pluginFolderName = "Plugin";
static void Main()
{
// プラグインのフォルダーへのフルパス名を取得し、
var pluginFolderName = GetPluginFolderName();
// もしプラグインのフォルダーが在ったら、
if (Directory.Exists(pluginFolderName))
// プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
Directory.GetFiles(pluginFolderName, "*.*").ToList().ForEach(Run);
}
// プラグインのフォルダーへのフルパス名を取得
static string GetPluginFolderName()
{
// 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
}
// プラグインを実行
static void Run(string pluginPath)
{
switch (Path.GetExtension(pluginPath).ToLower()) {
case ".dll": /* DLL の場合       */ RunDll(pluginPath); break;
case ".cs" : /* C# のコードの場合 */ RunCSharp(pluginPath); break;
case ".py": /* Python のコードの場合 */ RunPython(pluginPath); break;
}
}
// DLL プラグインを実行
static void RunDll(string path)
{
// DLL をアセンブリとして読み込む
var assembly = Assembly.LoadFrom(path);
if (assembly != null)
// アセンブリをプラグインとして実行
Run(assembly);
}
// C# プラグインを実行
static void RunCSharp(string pathName)
{
// C# のコードをアセンブリに変換
var assembly = CodeToAssembly(pathName);
if (assembly != null)
// アセンブリに変換されたプラグインを実行
Run(assembly);
}
// Python プラグインを実行
static void RunPython(string pathName)
{
dynamic plugin = Python.CreateRuntime().UseFile(pathName); // Python のコードからランタイムを作成し、
Run(plugin); // それを実行
}
// プラグインを実行
static void Run(Assembly pluginAssembly)
{
// アセンブリを読み込み、その中から public な最初のクラスを取り出す
var pluginType = pluginAssembly.GetExportedTypes().FirstOrDefault(type => type.IsClass);
if (pluginType != null)
// インスタンスを生成し、それをプラグインとして実行
Run(Activator.CreateInstance(pluginType));
}
// プラグインを実行
static void Run(dynamic plugin)
{
Console.WriteLine(plugin.Name()); // プラグインの Name を表示し、
plugin.Run(); // プラグインを Run
}
// C# のコードをコンパイルしてアセンブリに変換
public static Assembly CodeToAssembly(string csharpCode)
{
using (var cscp = new CSharpCodeProvider()) {
// コンパイルした結果のアセンブリを返す
return cscp.CompileAssemblyFromFile(new CompilerParameters { GenerateInMemory = true }, csharpCode).CompiledAssembly;
}
}
}
  • DLL プラグインは前回と同じ。
    アセンブリとして読み込んで、その中から public な最初のクラスを取り出し、中のメソッドを dynamic に実行する。
  • C# プラグインはコンパイルし、メモリ上でアセンブリに変換する。後は DLL プラグインと同じ。
  • Python プラグインは、コンパイルせずに、動的にスクリプトとして実行することでメソッドを呼び出す。

dynamic を使うことで、Python のプラグインも同じように実行することができる。

■ プラグイン処理の実行例

では、実行してみよう。

プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成し、そこに三つのプラグイン "Test.dll"、"Test.cs"、"Test.py" を置く。

Plugin フォルダーの中に置かれた三つのプラグイン
Plugin フォルダーの中に置かれた三つのプラグイン

本体プログラムの実行結果は次の通りだ。

DLL Plugin
DLL Plugin is running!
C# Plugin
C# Plugin is running!
Python plugin
Python plugin is running!

各プラグインが実行された。

■ 今回のまとめ

今回は、前回のプラグイン処理に少し補足を行った。

Visual Basic.NET や F#、IronRuby 等も同様に扱えるのでないだろうか。