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

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

プラグイン処理

Dynamic

動的処理の一例として、今回はプラグイン処理を行ってみる。

プラグイン処理によって、アプリケーションに対して動的に機能を追加できるようにすることができる。

■ プラグイン処理の例

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

  • 実行中のプログラムがあるフォルダーの下の Plugin と云う名前のフォルダーにある dll ファイルをプラグインのアセンブリと看做す。
  • プラグインのアセンブリの中にある最初の public なクラスをプラグインと見做す。
  • プラグインは、必ず Name と云う名称を表す public なプロパティを持っている。
  • プラグインは、必ず void Run() と云う public なメソッドによって動作する。
・プラグイン処理の実装の例

では、実装してみよう。

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

先ずプラグイン側だ。

プラグインは、クラスライブラリとして作成する。

public なクラスを持ち、その中には、public な Name プロパティと public な Run() メソッドを持てば良い。

using System;

public class Plugin
{
    public string Name
    {
        get { return "Sample Plugin"; }
    }

    public void Run()
    {
        Console.WriteLine("This is sample plugin!");
    }
}
・プラグインが組み込まれる本体側の実装の例

次はプラグインが組み込まれる本体側だ。

using System;
using System.IO;
using System.Linq;
using System.Reflection;

class Program
{
    // プラグインのフォルダー名
    const string pluginFolderName = "Plugin";
    // 拡張子が dll のものをプラグインと看做す
    const string pluginFileName = "*.dll";
    
    static void Main()
    {
        // プラグインのフォルダーへのフルパス名を取得し、
        var pluginFolderName = GetPluginFolderName();
        // もしプラグインのフォルダーが在ったら、
        if (Directory.Exists(pluginFolderName))
            // プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
            Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
    }

    // プラグインのフォルダーへのフルパス名を取得
    static string GetPluginFolderName()
    {
        // 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
    }

    // プラグインを実行
    static void Run(string pluginPath)
    {
        // アセンブリを読み込み、その中から public な最初のクラスを取り出す
        var pluginType = Assembly.LoadFrom(pluginPath).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
    }
}

規約ベースなので、特にプラグイン側は interface 等は持っていないし、本体側も interface で探したりしていない。

規約通りのものを実行する。

双方が特定の interface に依存しているのでなく、規約に依存している。

こういう場合は、dynamic による処理が合っている。

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

実行してみよう。

先ずは、Plugin フォルダーを準備しない儘で実行してみる。


何も表示されない。

次に、プログラムを実行しているフォルダーの下に Plugin と云う名前のフォルダーを作成して実行してみる。


矢張り、何も表示されない。

次に、クラスライブラリであるプラグインをビルドし、出来上がった dll ファイルを Plugin フォルダーの直下にコピーし、実行してみる。

Sample Plugin
This is sample plugin!

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

Plugin フォルダーの dll を二つにしてみると、

Sample Plugin
This is sample plugin!
Sample Plugin
This is sample plugin!

両方のプラグインが実行される。

・interface を用いたプラグイン処理の実装の例

因みに、interface を用いた場合の例だと次のようになる。

・interface を用いたプラグイン処理の実装の例 - interface

先ず interface をクラス ライブラリとして準備する。

public interface IPlugin
{
    string Name { get; }
    void Run();
}
・interface を用いたプラグイン処理の実装の例 - プラグイン側

プラグイン側で interface のライブラリを参照し、クラスで IPlugin を実装する。

public class Plugin : IPlugin
{
    public string Name
    {
        get { return "Sample Plugin"; }
    }

    public void Run()
    {
        Console.WriteLine("This is sample plugin!");
    }
}
・interface を用いたプラグイン処理の実装の例 - プラグインが組み込まれる本体側

本体側でも interface のライブラリを参照する。

プラグイン側と本体側が同じ interface に依存することになる。

interface を持ったクラスを探して、interface によって Name や Run() にアクセスする。

先の例と違って、dynamic は使わない。

using System;
using System.IO;
using System.Linq;
using System.Reflection;

class Program
{
    // プラグインのフォルダー名
    const string pluginFolderName = "Plugin";
    // 拡張子が dll のものをプラグインと看做す
    const string pluginFileName = "*.dll";
    
    static void Main()
    {
        // プラグインのフォルダーへのフルパス名を取得し、
        var pluginFolderName = GetPluginFolderName();
        // もしプラグインのフォルダーが在ったら、
        if (Directory.Exists(pluginFolderName))
            // プラグインのフォルダーの各のプラグインのパス名を使って、プラグインを実行
            Directory.GetFiles(pluginFolderName, pluginFileName).ToList().ForEach(Run);
    }

    // プラグインのフォルダーへのフルパス名を取得
    static string GetPluginFolderName()
    {
        // 実行中のこのプログラム自体のアセンブリのパスのフォルダーの下の Plugin フォルダーへのフルパスを取得
        return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + Path.DirectorySeparatorChar + pluginFolderName;
    }

    // プラグインを実行
    static void Run(string pluginPath)
    {
        // アセンブリを読み込み、その中から public で IPlugin インタフェイスを持った最初のクラスを取り出す
        var pluginType = Assembly.LoadFrom(pluginPath).GetExportedTypes().FirstOrDefault(type => type.IsClass && typeof(IPlugin).IsAssignableFrom(type));
        if (pluginType != null)
            // インスタンスを生成し、それをプラグインとして実行
            Run*1;
    }

    // プラグインを実行
    static void Run(IPlugin plugin)
    {
        Console.WriteLine(plugin.Name); // プラグインの Name を表示し、
        plugin.Run(); // プラグインを Run
    }
}

■ 今回のまとめ

今回は、プラグイン処理をやってみた。

interface を使ってやることもできるが、規約ベースで dynamic を使ってやる方法の方が依存関係がシンプルになる。

dynamic な動作は、実行時にオーバーヘッドが大きいので、そこは要注意だが、応用例を考えてみると面白いのではないだろうか。

*1:IPlugin)Activator.CreateInstance(pluginType