Pluginの実装3

IWidgetの実装クラスの検索は、WidgetPluginクラスのstaticメソッドとして実装しています。


クラスの検索にはアセンブリをロードする必要がありますが、使用しないアセンブリは検索処理後にはアンロードしたいところです。
アンロードはアプリケーションドメイン単位でしかできないので、ここでは検索処理専用のアプリケーションドメインを作成して、その中で処理を行う事にします。


呼び出し側アプリケーションで実行されるコードはこんな感じです。

public static WidgetPlugin[] FindWidget(string floder)
{
    WidgetPlugin[] widgets = null;
    AppDomain outer = null;

    try
    {
        outer = AppDomain.CreateDomain( "FindWidget" );
        outer.SetData( "FindWidgetOnOuterDomain_Param", floder );
        outer.DoCallBack( new CrossAppDomainDelegate( WidgetPlugin.FindWidgetOnOuterDomain ) );
        widgets = (WidgetPlugin[])outer.GetData( "FindWidgetOnOuterDomain_Result" );
    }
    catch
    {
    }
    finally
    {
        if ( outer != null )
        {
            AppDomain.Unload( outer );
        }
    }

    return ( widgets );
}

検索用アプリケーションドメインを作成して、AppDomain.DoCallBack()によりそのアプリケーションドメインで処理を実行します。
アプリケーションドメインを越えるパラメータのやりとりについては、AppDomain.SetData()、AppDomain.GetData()を使用します。

で、以下がクロスドメインで実行される検索処理になります。

private static void FindWidgetOnOuterDomain()
{
    string folder = (string)AppDomain.CurrentDomain.GetData( "FindWidgetOnOuterDomain_Param" );
    string[] dlls = Directory.GetFiles( folder, "*.dll" );

    List<WidgetPlugin> list = new List<WidgetPlugin>();

    Type widgetType = typeof( IWidget );

    foreach( string dll in dlls )
    {
        try
        {
            Assembly assembly = Assembly.LoadFrom( dll );

            foreach( Type t in assembly.GetExportedTypes() )
            {
                if ( ( widgetType.IsAssignableFrom( t ) == true ) &&
                     ( t.IsClass == true ) &&
                     ( t.IsAbstract == false ) )
                {
                    string name = t.FullName;
                    string version = "";
                    string description = "";

                    WidgetInfoAttribute attr = (WidgetInfoAttribute)Attribute.GetCustomAttribute( t, typeof( WidgetInfoAttribute ) );
                    if ( attr != null )
                    {
                        name = ( attr.Name != null ? attr.Name : "" );
                        version = ( attr.Version != null ? attr.Version : "" );
                        description = ( attr.Description != null ? attr.Description : "" );
                    }

                    list.Add( new WidgetPlugin( dll, t.FullName, name, version, description ) );
                }
            }
        }
        catch
        {
        }
    }

    WidgetPlugin[] widgets = list.ToArray();
    AppDomain.CurrentDomain.SetData( "FindWidgetOnOuterDomain_Result", widgets );
}

指定フォルダのDLL一覧を検索して、アセンブリ中からIWidget派生なもの(ClassでAbstractじゃないもの)を探す処理になります。
該当するクラスが見つかった場合はメタ情報としてWidgetInfoAttributeを取得し、収集した情報からWidgetPlugin情報を作成しています。
#とりあえずフォルダの再帰検索は入ってません


処理結果の戻りがCollectionではなく配列なのは、オブジェクトがAppDomainを越えるためにはSerializableであるか、またはMarshalByRefObject派生である必要があるためです。


Serializableなオブジェクトはドメインを越えてコピーされますし、MarshalByRefObjectは別アプリケーションドメインからはProxy経由で参照されます。
この辺の正確なことはEssential.NETを読むといいですね。

Essential .NET ― 共通言語ランタイムの本質

Essential .NET ― 共通言語ランタイムの本質


で、ここまででPluginの情報収集はできるようになったので、次はPluginのロード・アンロード処理になります。