今回は、指定フォルダ配下にある、指定I/Fを実装したアセンブリを、動的に読み込んでインスタンスを生成する実装を行います。
目次
- ソリューション構成
- 取り決めごとにする インターフェース の作成
- プラグイン実態(dllファイル) の 実装
- プラグイン を読み込んで インスタンス の生成
サンプルコード
ソリューション構成
プロジェクトは以下に示す3つを作ります。
- MainWindow
dll動的読み込みをテストする実行可能ファイル。WPFとかWindowsFormsApplicationとか…。IPlugin プロジェクト を参照します。 - IPlugin
取り決めごとにするインターフェース。クラスライブラリ。MainWindow と SamplePlugin から参照される。 - SamplePlugin
プラグインの実態。クラスライブラリ。IPlugin プロジェクト を参照します。
取り決めごとにする インターフェース の作成
IPlugin.cs
namespace Sample { using System; using System.Collections.Generic; using System.Linq; using System.Text; public interface IPlugin { string Message(); } }
インターフェースは Message
を返すだけの単純なものを設定します。
必要であればこの インターフェース を追加、修正していきます。
プラグイン実態(dllファイル) の 実装
SamplePlugin.cs
namespace Sample { using System; using System.Collections.Generic; using System.Linq; using System.Text; public class Class1 : IPlugin { public string Message() { return "こんちわ"; } } }
インターフェース を実装しただけです。
プラグイン を読み込んで インスタンス の生成
MainWindow.xaml.cs
namespace Sample { using System.IO; using System.Reflection; using System.Windows; public partial class MainWindow : Window { /// <summary> /// 実行ボタンを押下したとき呼び出されます。 /// </summary> /// <param name="sender">イベント発生元</param> /// <param name="e">イベントオブジェクト</param> private void btnExecute_Click(object sender, RoutedEventArgs e) { var interfaceName = typeof(IPlugin).FullName; var typeName = string.Empty; IPlugin plugin = null; // プラグインファイルパス一覧の取得 var pluginFilePathList = this.GetPluginFilePathList(); // プラグインからインスタンス生成 foreach (var pluginFilePath in pluginFilePathList) { plugin = this.CreateInstance<IPlugin>(pluginFilePath); if (plugin == null) { continue; } this.textBox_filename.Text += plugin.Message(); } } /// <summary> /// プラグインファイルパス一覧を取得します。 /// </summary> /// <returns>プラグインへのファイルパス一覧</returns> private string[] GetPluginFilePathList() { var entryDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); var pluginDirectory = @"plugin"; var targetDirectory = Path.Combine(entryDirectory, pluginDirectory); if (!Directory.Exists(targetDirectory)) { return new string[] { }; } return Directory.GetFiles(targetDirectory, "*.dll", SearchOption.AllDirectories); } /// <summary> /// 指定された dll のインスタンスを生成します。 /// </summary> /// <typeparam name="T">インターフェース クラス</typeparam> /// <param name="filepath">プラグイン ファイルパス</param> /// <returns>プラグイン から生成 されたインスタンス</returns> private T CreateInstance<T>(string filepath) where T : class { Assembly assembly = null; string typeName = string.Empty; string interfaceName = typeof(T).FullName; assembly = Assembly.LoadFrom(filepath); if (assembly == null) { return null; } foreach (var type in assembly.GetTypes()) { if (type.IsClass && type.IsPublic && !type.IsAbstract && type.GetInterface(interfaceName) != null) { typeName = type.FullName; break; } } return assembly.CreateInstance(typeName) as T; } } }
- btnExecute_Click(object, RoutedEventArgs) : void
エントリーポイント。 ボタンが押されたときに dll を読み込んでインスタンス生成を始めます。
- GetPluginFilePathList() : string[]
プラグイン dll ファイルパス一覧 を取得します。
実行可能ファイルから見て、"plugin" の相対位置にあるフォルダ配下に含まれる拡張子 "*.dll" を探し出します。
実行可能ファイルの場所は Assembly.GetEntryAssembly().Location
で取得します。
指定フォルダ以下のファイル検索は Directory.GetFiles()
で取得します。
- CreateInstance<T>(string) : T
プラグイン dll への絶対パスを引数にとって インスタンス を生成します。
指定されたインターフェースを実装したクラスのインスタンスを生成して返します。
アセンブリ は Assembly.LoadFrom()
で読み込み、 assembly.CreateInstance()
で インスタンス生成 します。
try-catch を行っていないので、適宜追加実装してください。。
おまけ
以下のような ビルド イベント を登録しておくと、コピーしなくて済むので便利です。
TARGET_PRJ
の値は任意に書き換えてください。
set MODE=$(ConfigurationName) set SOLUTION_DIR=$(SolutionDir) set SOURCE_PRJ=$(ProjectName) set TARGET_PRJ=Application set PLUGIN_DIR=plugin set SOURCE_DIR=%SOLUTION_DIR%%SOURCE_PRJ%\bin\%MODE%\*.dll set TARGET_DIR=%SOLUTION_DIR%%TARGET_PRJ%\bin\%MODE%\%PLUGIN_DIR% xcopy "%SOURCE_DIR%" "%TARGET_DIR%" /e /c /i /r /y /f
今回、以下のサイトを参考にしました。
- MSDN - Assembly クラス
- MSDN - Assembly.CreateInstance メソッド (String)
- MSDN - Assembly.Load メソッド (String)
- MSDN - Directory クラス
- MSDN - Directory.GetFiles メソッド (String, String, SearchOption)
最後に… このブログに興味を持っていただけた方は、 ぜひ 「Facebookページ に いいね!」または 「Twitter の フォロー」 お願いします!!