プログラミング C# - 翔ソフトウェア (Sho's)

C#/.NET/ソフトウェア開発など

プラグイン処理 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 等も同様に扱えるのでないだろうか。