今回は、指定フォルダ配下にある、指定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 の フォロー」 お願いします!!