17

時間がない人のために、問題を短いバージョンと長いバージョンに分けました。

短縮版:

プロバイダーとコンシューマーのプラグインを備えたシステムのアーキテクチャが必要です。プロバイダーはインターフェイス IProvider を実装し、コンシューマーは IConsumer を実装する必要があります。実行中のアプリケーションは、IProvider と IConsumer のみを認識する必要があります。コンシューマーの実装は、InterfaceX を実装して List を取得するプロバイダーを (ServiceProcessor を使用して) 実行中のアセンブリに問い合わせることができます。これらの IProvider オブジェクトは、InterfaceX が定義するいくつかのイベントにコンシューマーをフックできるように (コンシューマー内で) InterfaceX にキャストする必要があります。これは、実行中のアセンブリがこの InterfaceX 型を認識していない (キャストが失敗する) ため、失敗します。解決策は、プラグインと実行中のアセンブリの両方が参照するアセンブリに InterfaceX を含めることですが、これは新しいプロバイダー/コンシューマーのペアごとに再コンパイルする必要があり、非常に望ましくありません。

助言がありますか?

長いバージョン:

より高いレベルの再利用性を実現するためにプラグインを使用する、ある種の汎用サービスを開発しています。このサービスは、プロバイダーとコンシューマーを使用したある種のオブザーバー パターン実装で構成されています。プロバイダーとコンシューマーの両方が、メイン アプリケーションのプラグインである必要があります。最初に、ソリューションに含まれているプロジェクトをリストして、サービスがどのように機能するかを説明しましょう。

プロジェクト A: すべてのプラグインと基本機能をホストする Windows サービス プロジェクト。デバッグを容易にするために、TestGUI Windows Forms プロジェクトが使用されます。プロジェクト B の ServiceProcessor クラスのインスタンスは、プラグイン関連の作業を行っています。このプロジェクトのサブフォルダー "Consumers" と "Providers" にはサブフォルダーが含まれており、各サブフォルダーにはそれぞれコンシューマーまたはプロバイダーのプラグイン アセンブリが含まれています。

プロジェクト B: ServiceProcessor クラス (すべてのプラグインのロードとプラグイン間のディスパッチなどを行う)、IConsumer および IProvider を保持するクラス ライブラリ。

プロジェクト C: プロジェクト B にリンクされたクラス ライブラリで、TestConsumer (IConsumer を実装) と TestProvider (IProvider を実装) で構成されます。追加のインターフェイス (ITest、それ自体は IProvider から派生) は、TestProvider によって実装されます。

ここでの目標は、Consumer プラグインが ServiceProcessor に (少なくとも IProvider を実装する) プロバイダーを問い合わせることができるようにすることです。返された IProvider オブジェクトは、コンシューマーがイベント ハンドラーを ITest イベントにフックできるように、IConsumer 実装で実装する他のインターフェイス (ITest) にキャストする必要があります。

プロジェクト A が開始されると、コンシューマー プラグインとプロバイダー プラグインを含むサブフォルダーが読み込まれます。以下は、これまでに遭遇し、解決しようとしたいくつかの問題です。

これは、TestProvider と TestConsumer が認識しているメソッドとイベントにのみ適用されるため、プロジェクト C に常駐していたインターフェイス ITest です。一般的な考え方は、プロジェクト A をシンプルに保ち、プラグインが互いに何をするかを意識しないことです。

プロジェクト C の ITest と、IProvider を ITest にキャストする TestConsumer の Initialize メソッドのコード (ITest を実装するオブジェクトが IConsumer オブジェクトとして知られている場合、単一のクラス ライブラリ自体で失敗することはありません) を使用すると、無効なキャスト エラーが発生します。 . このエラーは、プロジェクト A からも参照されるプロジェクト B に ITest インターフェイスを配置することで解決できます。ただし、新しいインターフェイスをビルドするときにプロジェクト A を再コンパイルする必要があるため、これは非常に望ましくありません。

プロバイダーとコンシューマーのみがこのインターフェイスを認識する必要があるため、プロジェクト C のみによって参照される単一のクラス ライブラリに ITest を配置しようとしましたが、成功しませんでした。プラグインをロードすると、CLR は参照されたプロジェクトが見つからないと述べています。これは、現在の AppDomain の AssemblyResolve イベントにフックすることで解決できますが、どういうわけかこれも望ましくないようです。ITest は再びプロジェクト B に戻りました。

プロジェクト C をコンシューマーとプロバイダー用の別のプロジェクトに分割しようとしましたが、どちらもそれ自体がうまく機能するアセンブリを読み込んでいます。両方のアセンブリは、Assemblies コレクションまたは現在の AppDomain に常駐しています。 、Version=1.0.0.0、Culture=neutral、PublicKeyToken=2813de212e2efcd3 アセンブリが見つかりました: Datamex.Projects.Polaris.Testing.Consumers、Version=1.0.0.0、Culture=neutral、PublicKeyToken=ea5901de8cdcb258

コンシューマーはプロバイダーを使用するため、コンシューマーからプロバイダーへの参照が行われました。ここで、AssemblyResolve イベントが再び発生し、次のファイルが必要であることが示されました。

私の質問: これはなぜですか? このファイルはすでにロードされていますよね?IProvider から、実装がわかっているインターフェイスへのキャストが不可能なのはなぜですか? これはおそらく実行中のプログラム自身がこのインターフェースを知らないためだと思いますが、これを動的にロードすることはできないのでしょうか?

私の最終的な目標: コンシューマー プラグインは、Interface x を実装しているプロバイダーを ServiceProcessor に問い合わせます。プロバイダーは、インターフェイス x を認識してアセンブリを実行せずに、このインターフェイス x にキャストできます。

助けてくれる人?

前もってありがとう、エリック

4

5 に答える 5

16

できる限りソリューションを再作成しようとしましたが、そのような問題はありません。(警告、多くのコード サンプルが続きます....)

最初のプロジェクトはアプリケーションで、これには 1 つのクラスが含まれます。

public class PluginLoader : ILoader
{
    private List<Type> _providers = new List<Type>();

    public PluginLoader()
    {
        LoadProviders();
        LoadConsumers();
    }

    public IProvider RequestProvider(Type providerType)
    {
        foreach(Type t in _providers)
        {
            if (t.GetInterfaces().Contains(providerType))
            {
                return (IProvider)Activator.CreateInstance(t);
            }
        }
        return null;
    }

    private void LoadProviders()
    {
        DirectoryInfo di = new DirectoryInfo(PluginSearchPath);
        FileInfo[] assemblies = di.GetFiles("*.dll");
        foreach (FileInfo assembly in assemblies)
        {
            Assembly a = Assembly.LoadFrom(assembly.FullName);
            foreach (Type type in a.GetTypes())
            {
                if (type.GetInterfaces().Contains(typeof(IProvider)))
                {
                    _providers.Add(type);
                }
            }
        }

    }

    private void LoadConsumers()
    {
        DirectoryInfo di = new DirectoryInfo(PluginSearchPath);
        FileInfo[] assemblies = di.GetFiles("*.dll");
        foreach (FileInfo assembly in assemblies)
        {
            Assembly a = Assembly.LoadFrom(assembly.FullName);
            foreach (Type type in a.GetTypes())
            {
                if (type.GetInterfaces().Contains(typeof(IConsumer)))
                {
                    IConsumer consumer = (IConsumer)Activator.CreateInstance(type);
                    consumer.Initialize(this);
                }
            }
        }
    }

明らかに、これは非常に整理することができます。

次のプロジェクトは、次の 3 つのインターフェイスを含む共有ライブラリです。

public interface ILoader
{
    IProvider RequestProvider(Type providerType);
}

public interface IConsumer
{
    void Initialize(ILoader loader);
}

public interface IProvider
{
}

最後に、これらのクラスを含むプラグイン プロジェクトがあります。

public interface ITest : IProvider
{        
}

public class TestConsumer : IConsumer
{
    public void Initialize(ILoader loader)
    {
        ITest tester = (ITest)loader.RequestProvider(typeof (ITest));
    }
}

public class TestProvider : ITest
{        
}

アプリケーション プロジェクトとプラグイン プロジェクトの両方が共有プロジェクトを参照し、プラグイン dll がアプリケーションの検索ディレクトリにコピーされますが、相互に参照することはありません。

PluginLoader が構築されると、すべての IProvider が検出され、すべての IConsumer が作成され、それらに対して Initialize が呼び出されます。初期化の内部で、コンシューマーはローダーからプロバイダーを要求できます。このコードの場合、TestProvider が構築されて返されます。これはすべて、アセンブリの読み込みを巧妙に制御することなく機能します。

于 2009-05-06T14:18:23.813 に答える
4

これはまだ開発中ですが、MEF(.Net 4に含まれる予定)の完璧なユースケースのように聞こえ、VS2010で内部的に使用されます。

MEFは、実行時の拡張性の問題に対する簡単なソリューションを提供します。これまで、プラグインモデルをサポートしたいアプリケーションは、独自のインフラストラクチャを最初から作成する必要がありました。これらのプラグインは多くの場合アプリケーション固有であり、複数の実装間で再利用することはできませんでした。

プレビューはすでにhttp://www.codeplex.com/MEFで入手できます。

グレンブロックのブログも参考になります。

于 2009-05-06T14:27:51.763 に答える
2

私の記事は、プラグインフレームワークの実用的な例と、インターフェイスを保持する共通のアセンブリを作成することでこれらの問題にどのように対処するかを確認するのに役立つ場合があります。

C#基本チュートリアルのプラグイン:

http://www.codeproject.com/KB/cs/pluginsincsharp.aspx

Genericsが有効なプラグインマネージャーライブラリを使用したフォローアップ記事:

http://www.codeproject.com/KB/cs/ExtensionManagerLibrary.aspx

于 2009-05-06T14:26:49.643 に答える
0

私はあなたがやろうとしていることと同様のことをしました.ローダーが自動的に見える場所にアセンブリがある限り、問題は発生しませんでした.

exe が存在する場所の下のサブディレクトリにすべてのアセンブリを配置しようとしましたか? 今は詳細を思い出せませんが、ローダーがアセンブリ/タイプを探す正確な場所と順序について文書化された手順のリストがあります。

于 2009-05-06T13:53:40.457 に答える
0

質問が、2 つの無関係なアセンブリが同じインターフェイスを共有する方法である場合、答えは「できません」です。解決策は、すべてのアセンブリ、おそらくプラグイン ビルダーが参照できる dll、およびアセンブリを読み込んでいます。

于 2009-05-06T13:47:50.597 に答える