~いろいろなクラスを見てみよう~ 第03回 "System.Reflection.Assembly"
2007年10月10日 水曜日 | Paken > isa-inguz |
なんか前回のConvertは猫でもわかる(書籍版)に載っているそうです。自分はweb版しか読んでいないので知りませんでした。だってお金無いんだもんっ!
まあ本題に入りましょうか。
今回扱うのは System.Reflection.Assembly というクラスです。
名前、聞いたことありますか?多分滅多に使う事はないんじゃないでしょうか。自分も最近プラグインシステムの開発のために必要になって探すまで、存在を知りませんでした。
正直言って、滅多に使わないクラスですけど、これがあるのとないのとでは作成できるアプリケーションの拡張性が段違いですので、ちょっと見ておきましょう。
このクラスは、CLR用バイナリのアセンブリ ―簡単にいえば、.Net用実行ファイルやdllなどの中身そのもの― を表現します。
このクラスを使用すると、任意のバイナリに含まれるクラスなどを参照したり抽出したり再利用したり出来る訳です。
ただ、普通にプログラミングをしている上では、仮に幾つにアセンブリを分割しようとこのクラスの使用が必要になることはまずありません。
何故なら、VS上のプロジェクト内で複数のアセンブリに分割する場合は、大抵は各アセンブリは互いに依存参照設定を行ってからビルドしますから、ユーザーはプロジェクトごとに設定を行い、ソースコード内にusing文を書くだけで事が足りるからです。
つまりこれは、初めから必要なクラスが判明している場合は分割されたアセンブリの処理は全てIDEによって隠蔽されることができるという事です。
アセンブリ処理が隠蔽されている以上、わざわざAssemblyをオブジェクトとしてプログラマーは意識する必要は無かったという事です。
さて、それでは、そうではなくAssemblyをオブジェクトとしてプログラマーが意識しなくてはならない場合とは何でしょうか。
それは、初めに少しそれっぽい事を書きましたが、プラグインなど、ビルド時にはどのアセンブリを読みだすのか判明していない様な仕組みを実装する場合です。
例えば次のようなケースを考えてみましょう。
ここに大きく分けて三種類のアセンブリが存在する。
ひとつは実行ファイル、残りの二つはライブラリである。
ライブラリの一つにはプラグインの基本クラスとなる抽象クラスABSTが存在しており、メソッドMETHの実装を要求している。
実行ファイルは、カレントディレクトリ以下にあるDLLをすべて読み込んで、それらに含まれる抽象クラスABSTを継承するクラスを見つけると、そのインスタンスを生成し、メソッドMETHを実行する。
残るもう一方のライブラリには、抽象クラスABSTを継承した派生クラスが記述されている。
このプラグインライブラリは、ユーザーが自由に名前・内容ともに追加・削除・変更を加えることが出来る。ただし、ABSTの派生クラスは必ず含まれる。
こういった状況下においては、先ほどの様にビルド時にリンクを行っておくというのは明らかに不可能ですね。
そして、この問題を解決するために必要なのが、先ほどのAssemblyクラスである。という訳です。
では、実際に解決方法を示しながらこのクラスの詳細を解説していきましょう。
まず、このプログラムは動的にアセンブリを読みだすという作業を行っています。これはどうやって行うのでしょうか。
それには Assembly Assembly.LoadFrom(string assemblyfile) を使用します。
このメソッドはAssemblyクラスのスタティックメソッドで、引数に指定したファイル名を持つアセンブリを読み込んで返します。
これによってアセンブリのインスタンスが生成されます。
次に、アセンブリに特定クラスの派生クラスが存在することを確認していますね。それではこれはどうやるのでしょうか?
それには次の二つのメソッドを組み合わせます。
Type[] Assembly.GetExportedTypes()
bool Type.IsSubclassOf (Type t)
前者はAssemblyクラスのメンバメソッドで、そのアセンブリに定義されている、Export型(=publicに指定されたクラス)の配列を取得します。
後者はTypeクラスのメンバメソッドで、その型がある型から継承されているか否かを返すというものです。
実際にあるアセンブリに定義されている、あるアブストラクトクラスを継承する型のクラスを探すときには、GetExportedTypes()で読み込んだ型一つ一つに対して、foreach文などを使ってIsSubclassOf()を行ってtrueが帰ってくるのを待てば良い訳です。
また、そのクラスが抽象クラスではないかと言う事もついでに調べておきましょう。
これはbool Type.IsAbstractプロパティを見る事で分かります。
最後に、前作業によって見つけたお目当てのTypeのインスタンスを作って、そのメンバメソッドを実行しています。これが分かれば全部解決ですね。
これには MethodInfo Type.GetMethod (string name) を使います。
このメソッドもTypeクラスのメンバメソッドで、その型に含まれるnameという名前を持つメソッドを探して、見つかったらそのメソッドのMethodInfoインスタンスを返すというものです。見つからなかったらnullが返ります。
さてここで突然出てきたMethodInfoクラスですが、これはメソッドのメタデータを提供して―とか詳細を説明していると長くなるので、このクラスのインスタンスを使ってメソッドを実行する方法だけのご説明。
実行に使うのは MethodInfo.Invoke (Object obj, Object[] parameters) です。
objはそのメソッドを呼び出させるオブジェクトを指定し、parametersにはそのメソッドの引数を与えます。
ここでobjに与えるためのインスタンスが必要になります。
これは object Activator.CreateInstance(Type type) で簡単に行えます。
引数が必要なコンストラクタを持つようなクラスの場合にはオーバーロードされた別のメソッドを使いましょう。
さて、以上で基本的なプラグインローダールーチンの構築方法は分かったはずです。では、サンプルコードを。今回は先ほどの例に示した通り、三分割です。
//メインアセンブリ
using System;
using System.Reflection;
class AsseblyTest_exe
{
public static void Main() { string[] asmnames = GetAssemblyNames();
foreach (string asmname in asmnames) { Assembly asm = Assembly.LoadFrom(asmname);
foreach (Type extype in asm.GetExportedTypes()) { if (extype.IsSubclassOf(typeof(ABST)) && !extype.IsAbstract) { MethodInfo meth = extype.GetMethod("METH"); object obj = Activator.CreateInstance(extype);
if (meth != null) { meth.Invoke(obj, null); } } } } }
static string[] GetAssemblyNames() { //読みだすアセンブリ名を列挙する。具体的処理は略す。 } }
//基本クラスライブラリ Lib.dll
using System;
public abstract class ABST
{
public abstract void METH(); }
//プラグインライブラリ Plugin1.dll
using System;
public class Plugin1 : ABST
{
public override void METH() { Console.WriteLine("Hello, Plugin!"); } }
ーーー実行結果ーーー
Hello, Plugin!
ーーーここまでーーー
※読み込まれたアセンブリがPlugin1.dllのみの場合
お疲れ様でした。いえ、疲れたのは僕なのですが。
是非機会があれば今回の事を生かして何か組んでみてください。
あと、今回でちょっと疲れたので、次回はちょっと間が開きます。今度は多分今回よりは簡単な内容になるのでご安心を。
では。
コメント