2007 年 9 月 12 日 23 時 51 分

プラグインの動的ロード


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


プラグインを動的に読み込む所から考えてみよう。

現在 Dispatcher.cpp では、#using ディレクティブを使い、
<HelloSaver.dll> を直接参照しているため、
以下のコード断片だけでインスタンスを作成できる。

    // スクリーンセーバーを作成
    HelloSaver saver;

これを動的に読み込むように切り替えるためには、
まず、HelloSaver.dll への依存を外さなければならない。
そうなると、「HelloSaver」のように、
プラグイン実装のクラス型を直接使うことはできないため、
リフレクションを使ってアクセスすることになる。

まず最初に、アセンブリをロードし、
ISaver を実装している型を取得する。
この手順は以下の通り。

1. Assembly::LoadFrom を使ってアセンブリを読み込む
2. Assembly::GetType を使って名前で型を取得するか、
   Assembly::GetTypes を使って全ての型を得る。
3. Type が ISaver を実装し、利用可能かどうかを調べる。

コードで書くとこんな感じ。

    using namespace System;
    using namespace System::Collections::Generic;
    using namespace System::Reflection;
    using namespace Loafer::ScreenSaver::Plugins;

    IList<Type ^> ^LoadScreenSaverTypes(String ^assemblyPath) {

        // ISaver の Type を取得
        Type ^interfaceType = ISaver::typeid;
   
        // 戻り値用の Type の List を用意
        IList<Type ^> ^types = gcnew List<Type ^>();

        // アセンブリを AppDomain に動的ロード
        Assembly ^plugin = Assembly::LoadFrom(assemblyPath);
       
        // アセンブリに含まれる型を列挙
        for each (Type ^type in plugin->GetTypes()) {

            // public で抽象ではなく ISaver を実装し
            // デフォルトコンストラクタを持つ
            if (type->IsPublic && !type->IsAbstract
                    && interfaceType->IsAssignableFrom(type)
                    && type->GetConstructor(Type::EmptyTypes) != nullptr) {

                types->Add(type);

            }

        }

        return types;
   
    }

IList<Type ^> ^ は Generics の構文である。
その構文は C++ のテンプレートとほぼ同じである。
C++/CLI なので、IList も Type もハンドルとなる。

Assembly::GetTypes は配列を返す。
マネージ配列は IEnumerable を実装しているため、
C++/CLI で追加された for each 構文を使って列挙可能だ。

Type ^ が取得できたら、型の詳細を調べる。
Assembly::GetTypes は全ての型を返すので、
まず、public で abstract ではないクラスに限定し、
IsAssignableFrom で、ISaver と互換性があるか調べる。
最後に GetConstructor に Type::EmptyTypes を渡すことで、
引数なしの public コンストラクタがあるかどうか調べる。

これらをすべて満たしていれば、インスタンスを作成し、
ISaver にキャストできることが期待される型となる。

型 (Type ^) が取得できたら、
型からインスタンスを生成して ISaver にキャストする。
この手順は以下の通り。

4. Type::GetConstructor か Activator::CreateInstance で、
   Type が示すクラスのインスタンスを作成する。
5. 戻り値を ISaver にキャストする。

これはそれほど難しくない。

    ISaver ^ CreateScreenSaver(Type ^saverType) {

        // インスタンスを作成し
        Object ^instance = Activator::CreateInstance(saverType);

        // キャストして返す
        return safe_cast<ISaver ^>(instance);

    }

インスタンスの生成で最も手軽なのは、
専用のユーティリティクラスである Activator を使い、
Activator::CreateInstance を呼び出すことだ。

他にも、Type::GetConstructor 等で取得できる、
ConstructorInfo を使うこともできる。

    ConstructorInfo ^ctor = saverType->GetConstructor(Type::EmptyTypes);
    Object ^instance = ctor->Invoke(nullptr);

型からインスタンスを作成する方法はいくつもあるのだ。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.