8

私は .NET ベースのアプリケーションを構築しており、より拡張可能でプラグ可能な設計を可能にしたいと考えています。

簡単にするために、アプリケーションは一連の操作とイベントを公開します。

  • DoSomething()
  • DoOtherThing()
  • OnError
  • 成功時

「プラグイン」をロードして、これらの操作の一部にフックしたいと思います (次のようなものです: Event1 が起動したら、plugin1 を実行します)。

たとえば、 OnErrorイベントが発生したときにplugin1.HandleError()を実行します。

これは、イベント サブスクリプションで簡単に実行できます。

this.OnError += new Plugin1().HandleError();

問題はそれです:

  1. 私のアプリはタイプ「Plugin1」を認識していません (これはプラグインであり、私のアプリはそれを直接参照していません)。
  2. そうすることで、時間の前にプラグインがインスタンス化されますが、これはやりたくないことです。

「従来の」プラグイン モデルでは、アプリケーション (プラグインの「クライアント」) が特定のキー ポイントでプラグイン コードをロードして実行します。たとえば、特定の操作が実行されたときの画像処理アプリケーション)。

プラグイン コードをインスタンス化するタイミングと実行するタイミングの制御は、クライアント アプリケーションに認識されています。

私のアプリケーションでは、プラグイン自体がいつ実行するかを決定します (「プラグインは OnError イベントに登録する必要があります」)。

プラグインの「実行」コードを「登録」コードと一緒に保持すると、登録時にプラグイン DLL がメモリにロードされるという問題が発生します。

たとえば、プラグイン DLL に Register() メソッドを追加する場合、Register メソッドを呼び出すには、プラグイン DLL をメモリにロードする必要があります。

この特定の問題に対する適切な設計ソリューションは何でしょうか?

  • プラグイン DLL のレイジー ロード (またはレイジー/イーガー ロードの提供)。
  • プラグインがフックするシステム/アプリのさまざまな部分を制御できるようにします。
4

4 に答える 4

5

存在しない問題を解決しようとしています。コードが Assembly.LoadFrom() を呼び出すときにすべての型が読み込まれるというイメージは間違っています。.NET フレームワークは、Windows がデマンド ページ仮想メモリ オペレーティング システムであることを最大限に活用します。そしてその後、いくつかの。

LoadFrom() を呼び出すとボールが転がります。は、CLRに、コア オペレーティング システムの抽象化であるメモリ マップト ファイルを作成させます。AppDomain に常駐しているアセンブリを追跡するために、内部状態を少し更新しますが、これは非常にマイナーです。MMF はメモリ マッピングをセットアップし、ファイルの内容をマップする仮想メモリ ページを作成します。プロセッサの TLB にある小さな記述子です。アセンブリ ファイルから実際には何も読み取られません。

次に、リフレクションを使用して、インターフェイスを実装する型を見つけようとします。これにより、CLR はアセンブリからアセンブリ メタデータの一部を読み取ります。この時点で、ページ フォールトにより、プロセッサは、アセンブリのメタデータ セクションをカバーする一部のページのコンテンツを RAM にマップします。数キロバイト、アセンブリに多くの型が含まれている場合はそれ以上になる可能性があります。

次に、ジャストインタイム コンパイラが動作を開始し、コンストラクターのコードを生成します。これにより、プロセッサーは、コンストラクター IL を含むページを RAM にフォールトさせます。

など。核となるアイデアは、必要な場合にのみ、アセンブリ コンテンツが常に遅延して読み取られるということです。このメカニズムはプラグインでも同じで、ソリューション内の通常のアセンブリと同じように機能します。順序がわずかに異なるという唯一の違いがあります。最初にアセンブリをロードしてから、すぐにコンストラクターを呼び出します。コードと CLR で型のコンストラクターを呼び出してから、すぐにアセンブリを読み込むのとは対照的です。同じくらい時間がかかります。

于 2012-04-25T00:05:58.903 に答える
4

必要なことは、dll のパスを見つけて、そこからアセンブリ オブジェクトを作成することです。そこから、取得したいクラスを取得する必要があります (たとえば、インターフェイスを実装するもの):

var assembly = Assembly.Load(AssemblyName.GetAssemblyName(fileFullName));
foreach (Type t in assembly.GetTypes())
{
  if (!typeof(IMyPluginInterface).IsAssignableFrom(t)) continue;
  var instance = Activator.CreateInstance(t) as IMyPluginInterface;
  //Voila, you have lazy loaded the plugin dll and hooked the plugin class to your code
}

もちろん、ここからは自由に、メソッドを使用したり、イベントにサブスクライブしたりできます。

于 2012-04-24T22:15:51.770 に答える
2

@Oskarの回答に従って手動でロードできますが、プラグインアセンブリをロードするために、IoCコンテナに頼ってディレクトリからアセンブリをロードする傾向があります(私はStructureMapを使用しています)。

アプリケーションの実行中にプラグインのロードをサポートしたい場合は、StructureMap を「再構成」して、新しいプラグインを選択することができます。

アプリケーション フックでは、イベントをイベント バスにディスパッチできます。以下の例では、StructureMap を使用して登録済みのすべてのイベント ハンドラーを検索していますが、単純な古いリフレクションまたは別の IoC コンテナーを使用することもできます。

public interface IEvent { }
public interface IHandle<TEvent> where TEvent : IEvent {
    void Handle(TEvent e);
}

public static class EventBus {
    public static void RaiseEvent(TEvent e) where TEvent : IEvent {
        foreach (var handler in ObjectFactory.GetAllInstances<IHandle<TEvent>>())
            handler.Handle(e);
    }
}

次に、次のようにイベントを発生させることができます。

public class Foo {  
    public Foo() {
        EventBus.RaiseEvent(new FooCreatedEvent { Created = DateTime.UtcNow });
    }
}

public class FooCreatedEvent : IEvent {
    public DateTime Created {get;set;}
}

そして、次のように(たとえば、プラグインで)処理します。

public class FooCreatedEventHandler : IHandle<FooCreatedEvent> {
    public void Handle(FooCreatedEvent e) {
        Logger.Log("Foo created on " + e.Created.ToString());
    }
}

プラグ可能なアプリケーションの開発に関する多くの問題を扱っている、Shannon Deminick によるこの記事をぜひお勧めします。これは、独自の「プラグイン マネージャー」のベースとして使用したものです。

個人的には、必要に応じてアセンブリをロードすることは避けます。IMO では、実行中のアプリケーションのユーザーが必要なプラグインがロードされるのを待たなければならないよりも、起動時間を少し長くすることをお勧めします (Web アプリケーションの問題はさらに少なくなります) 。

于 2012-04-24T22:32:56.963 に答える
1

Managed Extensibility Frameworkはあなたのニーズに合いますか?

于 2012-04-24T22:52:58.343 に答える