今日は、プラグイン側にもっと自由を与えてみたいと思います。
プラグインには、あの大空に翼を広げ飛んで行ってもらいます。(でも見えるところにいてくれないと困るけど)
呼び出すクラスと、呼び出す関数は、アプリケーション側では制御せず、プラグインを作成するタイミングで自由に決めることができます。
使った機能
- カスタム属性
- Activator
- Invoke
構成としては、
- アプリケーション(exe本体)
- プラグイン識別用の属性定義(DLL)
- プラグイン(DLL)
の3部作です。アプリケーションとプラグインは、属性定義(DLL)を参照しています。
では、さっそくサンプルコードです。
アプリケーション(exe)の実装
アプリケーション本体としては、次のような処理をしているだけです。
- 任意のフォルダーからdllを検索
- アセンブリ属性の確認(期待する属性の定義があれば、プラグインとみなす)
- タイプの取得(期待する属性の定義があれば、プラグインクラスとみなす)
- メソッドを呼び出す。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using PlugInAttribute; | |
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
namespace ConsoleApp | |
{ | |
//------------------------------------------------------- | |
/// <summary> | |
/// メソッド情報保持 | |
/// </summary> | |
//------------------------------------------------------- | |
public class PlugInMethod | |
{ | |
public Type ExeType { get; set; } | |
public IList<String> MethodNames { get; set; } | |
public PlugInMethod( Type type, IList<String> methods ) | |
{ | |
this.ExeType = type; | |
this.MethodNames = methods; | |
} | |
} | |
//------------------------------------------------------- | |
/// <summary> | |
/// 実行クラス | |
/// </summary> | |
//------------------------------------------------------- | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
var myAsm = Assembly.GetEntryAssembly(); | |
var exePath = new Uri(myAsm.Location).AbsolutePath; | |
var exeDir = Path.GetDirectoryName(exePath); | |
var methods = GetExecuteMethods( | |
exeDir, "dll", | |
SearchOption.TopDirectoryOnly); | |
// プラグインのメソッド呼び出し | |
foreach( var method in methods ) | |
{ | |
CallMethod( method ); | |
} | |
} | |
//------------------------------------------------------- | |
/// <summary> | |
/// 実行メソッド一覧取得 | |
/// </summary> | |
//------------------------------------------------------- | |
private static IList<PlugInMethod> GetExecuteMethods( | |
String dir, String extension, SearchOption seachOption ) | |
{ | |
var methods = new SortedList<Int32,PlugInMethod>(); | |
var dllPaths = Directory.GetFiles(dir,"*."+extension, | |
seachOption); | |
// 対象dll検索 | |
foreach( var dllPath in dllPaths ) | |
{ | |
var asmDll = GetPlugInAssembly(dllPath); | |
if( asmDll != null ) | |
{ | |
// 対象クラス検索 | |
var classTypes = asmDll.GetTypes(); | |
foreach( var classType in classTypes ) | |
{ | |
var typeAttrs = classType.GetCustomAttributes( | |
typeof(PlugInClassAttribute)); | |
if( typeAttrs == null || | |
typeAttrs.Count() == 0 ) | |
{ | |
continue; | |
} | |
var names = new SortedList<Int32,String>(); | |
var classIndex = 0; | |
foreach( var typeAttr in typeAttrs ) | |
{ | |
var plugInClass = typeAttr as PlugInClassAttribute; | |
if( plugInClass != null ) | |
{ | |
classIndex = plugInClass.ClassIndex; | |
names.Add( plugInClass.MethodIndex, | |
plugInClass.ExecuteName ); | |
} | |
} | |
// メソッド名を保持 | |
methods.Add( | |
classIndex, | |
new PlugInMethod( | |
classType, | |
names.Values ) ); | |
} | |
} | |
} | |
return methods.Values; | |
} | |
//------------------------------------------------------- | |
/// <summary> | |
/// PlugIn取得 | |
/// </summary> | |
//------------------------------------------------------- | |
private static Assembly GetPlugInAssembly( String dllPath ) | |
{ | |
var asmDll = Assembly.LoadFile(dllPath); | |
if( asmDll != null ) | |
{ | |
var asmAttr = asmDll.GetCustomAttribute( | |
typeof(PlugInAssemblyAttribute)); | |
if( asmAttr != null ) | |
{ | |
var asmPlugIn = asmAttr as PlugInAssemblyAttribute; | |
if( asmPlugIn != null ) | |
{ | |
return asmDll; | |
} | |
} | |
} | |
return null; | |
} | |
//------------------------------------------------------- | |
/// <summary> | |
/// メソッド呼び出し | |
/// </summary> | |
//------------------------------------------------------- | |
private static void CallMethod( PlugInMethod method ) | |
{ | |
var classType = method.ExeType; | |
var pluginInstance = Activator.CreateInstance(classType); | |
foreach( var methodName in method.MethodNames ) | |
{ | |
MethodInfo methodInfo = classType.GetMethod(methodName); | |
methodInfo.Invoke( pluginInstance, null ); | |
} | |
} | |
} | |
} |
プラグイン用属性定義(DLL)
属性定義では、次の2つのカスタム属性を定義しています。
- アセンブリ用のカスタム属性
- クラス用のカスタム属性
※クラス用のカスタム属性では、呼び出すクラスの順番、クラス内で呼び出す関数の順番を
指定できるようにしています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace PlugInAttribute | |
{ | |
//------------------------------------------------------- | |
/// <summary> | |
/// Assemblyがプラグインかどうかの判定をするための属性 | |
/// </summary> | |
//------------------------------------------------------- | |
[System.AttributeUsage(System.AttributeTargets.Assembly)] | |
public class PlugInAssemblyAttribute : Attribute | |
{ | |
public PlugInAssemblyAttribute() : base() | |
{ | |
} | |
} | |
//------------------------------------------------------- | |
/// <summary> | |
/// クラスがプラグインのクラスかどうかの判定をするための属性 | |
/// </summary> | |
//------------------------------------------------------- | |
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true )] | |
public class PlugInClassAttribute : Attribute | |
{ | |
public String ExecuteName { get; set; } | |
public Int32 ClassIndex { get; set; } | |
public Int32 MethodIndex { get; set; } | |
public PlugInClassAttribute( | |
String executeName, | |
Int32 classIndex, Int32 methodIndex):base() | |
{ | |
ExecuteName = executeName; | |
ClassIndex = classIndex; | |
MethodIndex = methodIndex; | |
} | |
} | |
} |
プラグイン(DLL)
プラグインでは、アセンブリのカスタム属性と、各クラスで、クラスのカスタム属性を記載しています。
テストのために、属性の定義をしていないクラス(Sample3)も記載しています。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
//このDllをプラグインとして認識させる | |
[assembly: PlugInAttribute.PlugInAssemblyAttribute] | |
namespace SamplePlugIn | |
{ | |
/// <summary> | |
/// プラグインクラス1 | |
/// </summary> | |
[PlugInAttribute.PlugInClass("ExecSampleA",2,1)] | |
[PlugInAttribute.PlugInClass("ExecSampleB",2,2)] | |
public class PlugInSample | |
{ | |
public PlugInSample(){} | |
public void ExecSampleB() | |
{ | |
Console.WriteLine( "Called SampleB" ); | |
} | |
public void ExecSampleA() | |
{ | |
Console.WriteLine("Called SampleA"); | |
} | |
} | |
/// <summary> | |
/// プラグインクラス2 | |
/// </summary> | |
[PlugInAttribute.PlugInClass("ExecSample2",1,1)] | |
public class Sample2 | |
{ | |
public Sample2() { } | |
public void ExecSample2() | |
{ | |
Console.WriteLine("Called Sample2"); | |
} | |
} | |
/// <summary> | |
/// 普通のクラス | |
/// </summary> | |
public class Sample3 | |
{ | |
public Sample3() { } | |
public void ExecSample3() | |
{ | |
Console.WriteLine("Called Sample3"); | |
} | |
} | |
} |
実行結果
実行結果は次のようになり、大成功でした。
Called Sample2
Called SampleA
Called SampleB
通常であれば、
ここまで、プラグインに自由を与えることはないと思います。
動的プラグインを作る際には、程よく使用していただければと思います。
(プラグインかどうかの判断で、アセンブリのカスタム属性くらいなら使うかも)
0 件のコメント:
コメントを投稿