トップ回答者
MEF(DirectoryCatalog)で特定のディレクトリ内のDLLのみを対象としたい

質問
-
.NET Framework 4.5
C# 2012
MEF(DirectoryCatalog)によってプラグインの仕組みを作ろうかと思っています。
プログラムは汚い状態ですが、以下の通りです。
var catalog1 = new DirectoryCatalog(@".\A2", "*.dll"); var container1 = new CompositionContainer(catalog1); container1.ComposeParts(this); Lazy<IAddinContract2Factory, IAddinContract2Metadata> factory2 = null; factory2 = factories2.FirstOrDefault(f => f.Metadata.ConverterName == "SampleA2"); var obj2 = factory2.Value.Create(); MessageBox.Show(obj2.Addin2Title);
ディレクトリ構成上はひとまず以下のように考えました。
hoge
├A2
│ └hoge.dll
└hoge.exeこれであれば特に問題ありません。
ところが以下の状態だと、fuga.dllを読み込んでしまいます。
hoge.dll、fuga.dllは同じロジックで、どちらが読み込まれているかを確認するために、出力内容だけ変更したものになります。
hoge
├A2
│ └hoge.dll
├fuga.dll
└hoge.exe
プログラムでは、A2ディレクトリ以下を対象にしろと言っているのに、exeと同じディレクトリ内にあるDLLまで読み込んでしまうようです。
特定のディレクトリ以下のDLLのみを対象とすることはできないのでしょうか?
回答
-
再現できないです…DirectoryCatalog.LoadFilesプロパティはどうなっていますか?
//Lib.dll namespace Lib { public interface ITest { string Name { get; } } public interface IMeta { string MetaName { get; } } } //fuga.dll namespace fuga { [System.ComponentModel.Composition.Export(typeof(Lib.ITest))] [System.ComponentModel.Composition.ExportMetadata("MetaName", "FUGA")] class A : Lib.ITest { public string Name { get { return "fuga"; } } } } //hoge.dll namespace hoge { [System.ComponentModel.Composition.Export(typeof(Lib.ITest))] [System.ComponentModel.Composition.ExportMetadata("MetaName", "HOGE")] public class A : Lib.ITest { public string Name { get { return "hoge"; } } } } //Test.exe using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using Lib; namespace MEFTest { class Program { static void Main(string[] args) { new Program().Test(); } [ImportMany(typeof(ITest))] public IEnumerable<Lazy<ITest, IMeta>> tests = null; void Test() { var path = System.IO.Path.GetDirectoryName(this.GetType().Assembly.Location); var pathA2 = System.IO.Path.Combine(path, "A2"); Console.WriteLine("*.dll"); Write1(path, "*.dll"); Console.WriteLine("*.exe"); Write1(path, "*.exe"); Console.WriteLine(@"A2\*.dll"); Write1(path, "*.exe"); Console.WriteLine("AggregateCatalog"); Write2(path,pathA2, "*.dll"); Console.ReadLine(); } private void Write1(string path, string filter) { var catalog1 = new DirectoryCatalog(path, filter); foreach (string s in catalog1.LoadedFiles) { Console.WriteLine(s); } var container1 = new CompositionContainer(catalog1); container1.ComposeParts(this); foreach (Lazy<ITest, IMeta> o in tests) { Console.WriteLine("\t" + o.Value.Name); } } private void Write2(string path1, string path2, string filter) { var catalog1 = new DirectoryCatalog(path1, filter); var catalog2 = new DirectoryCatalog(path2, filter); var agg=new AggregateCatalog(new DirectoryCatalog[] { catalog1, catalog2 }); var container1 = new CompositionContainer(agg); container1.ComposeParts(this); foreach (Lazy<ITest, IMeta> o in tests) { Console.WriteLine("\t" + o.Value.Name); } } } [Export(typeof(Lib.ITest))] [ExportMetadata("MetaName", "PIYO")] class Piyo : Lib.ITest { public string Name { get { return "piyo"; } } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク takiru 2017年12月15日 5:58
すべての返信
-
再現できないです…DirectoryCatalog.LoadFilesプロパティはどうなっていますか?
//Lib.dll namespace Lib { public interface ITest { string Name { get; } } public interface IMeta { string MetaName { get; } } } //fuga.dll namespace fuga { [System.ComponentModel.Composition.Export(typeof(Lib.ITest))] [System.ComponentModel.Composition.ExportMetadata("MetaName", "FUGA")] class A : Lib.ITest { public string Name { get { return "fuga"; } } } } //hoge.dll namespace hoge { [System.ComponentModel.Composition.Export(typeof(Lib.ITest))] [System.ComponentModel.Composition.ExportMetadata("MetaName", "HOGE")] public class A : Lib.ITest { public string Name { get { return "hoge"; } } } } //Test.exe using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using Lib; namespace MEFTest { class Program { static void Main(string[] args) { new Program().Test(); } [ImportMany(typeof(ITest))] public IEnumerable<Lazy<ITest, IMeta>> tests = null; void Test() { var path = System.IO.Path.GetDirectoryName(this.GetType().Assembly.Location); var pathA2 = System.IO.Path.Combine(path, "A2"); Console.WriteLine("*.dll"); Write1(path, "*.dll"); Console.WriteLine("*.exe"); Write1(path, "*.exe"); Console.WriteLine(@"A2\*.dll"); Write1(path, "*.exe"); Console.WriteLine("AggregateCatalog"); Write2(path,pathA2, "*.dll"); Console.ReadLine(); } private void Write1(string path, string filter) { var catalog1 = new DirectoryCatalog(path, filter); foreach (string s in catalog1.LoadedFiles) { Console.WriteLine(s); } var container1 = new CompositionContainer(catalog1); container1.ComposeParts(this); foreach (Lazy<ITest, IMeta> o in tests) { Console.WriteLine("\t" + o.Value.Name); } } private void Write2(string path1, string path2, string filter) { var catalog1 = new DirectoryCatalog(path1, filter); var catalog2 = new DirectoryCatalog(path2, filter); var agg=new AggregateCatalog(new DirectoryCatalog[] { catalog1, catalog2 }); var container1 = new CompositionContainer(agg); container1.ComposeParts(this); foreach (Lazy<ITest, IMeta> o in tests) { Console.WriteLine("\t" + o.Value.Name); } } } [Export(typeof(Lib.ITest))] [ExportMetadata("MetaName", "PIYO")] class Piyo : Lib.ITest { public string Name { get { return "piyo"; } } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答としてマーク takiru 2017年12月15日 5:58
-
返信が遅くなってしまいって申し訳ありません。
もしかしたら、分かった気がします。
現状のソースは以下の状態です。
// AddinContractA.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractA { public interface IAddinContractA { string AddinATitle { get; } void DoWork(); } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractA { public interface IAddinContractAFactory { IAddinContractA Create(); } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractA { public interface IAddinContractAMetadata { string ConverterName { get; } } } // AddinContractB.dll using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractB { public interface IAddinContractB { string AddinBTitle { get; } void DoWork(); } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractB { public interface IAddinContractBFactory { IAddinContractB Create(); } } using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinContractB { public interface IAddinContractBMetadata { string ConverterName { get; } } } // AddinA.dll using AddinContractA; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinA { [Export(typeof(IAddinContractAFactory))] [ExportMetadata("ConverterName", "AddinA")] public class AddinAFactory : IAddinContractAFactory { public IAddinContractA Create() { return new AddinASample(); } } } using AddinContractA; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinA { public class AddinASample : IAddinContractA { public string AddinATitle { get { return "Addin - A"; } } public void DoWork() { return; } } } // AddinB using AddinContractB; using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinB { [Export(typeof(IAddinContractBFactory))] [ExportMetadata("ConverterName", "AddinB")] public class AddinBFactory : IAddinContractBFactory { public IAddinContractB Create() { return new AddinBSample(); } } } using AddinContractB; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AddinB { public class AddinBSample : IAddinContractB { public string AddinBTitle { get { return "Addin - B1"; } } public void DoWork() { return; } } } // Forms.exe using AddinContractA; using AddinContractB; using System; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace Forms { public partial class Form1 : Form { public Form1() { InitializeComponent(); } [ImportMany] private IEnumerable<Lazy<IAddinContractAFactory, IAddinContractAMetadata>> factories { get; set; } [ImportMany] private IEnumerable<Lazy<IAddinContractBFactory, IAddinContractBMetadata>> factories2 { get; set; } private void button1_Click(object sender, EventArgs e) { var catalog1 = new DirectoryCatalog(@".\", "*.dll"); var container1 = new CompositionContainer(catalog1); container1.ComposeParts(this); foreach (var loadFile in catalog1.LoadedFiles) { MessageBox.Show(loadFile); } Lazy<IAddinContractAFactory, IAddinContractAMetadata> factory = null; factory = factories.FirstOrDefault(f => f.Metadata.ConverterName == "AddinA"); var obj = factory.Value.Create(); container1.Dispose(); MessageBox.Show(obj.AddinATitle); catalog1 = new DirectoryCatalog(@".\A2", "*.dll"); container1 = new CompositionContainer(catalog1); container1.ComposeParts(this); foreach (var loadFile in catalog1.LoadedFiles) { MessageBox.Show(loadFile); } Lazy<IAddinContractBFactory, IAddinContractBMetadata> factory2 = null; factory2 = factories2.FirstOrDefault(f => f.Metadata.ConverterName == "AddinB"); var obj2 = factory2.Value.Create(); container1.Dispose(); MessageBox.Show(obj2.AddinBTitle); } } }
ディレクトリ構成は以下になります。
Debug
├A2
│ ├AddinB1.dll
├AddinA.dll
├AddinB2.dll
├AddinContractA.dll
├AddinContractB.dll
├Forms.exe
└Forms.exe.configAddinB1.dll、AddinB2.dllがどのように作成されるかというと、AddinBのソースから、戻り値を"Addin - B1"としてビルドしたものをAddinB1.dll、"Addin - B2"としてビルドしたものを、AddinB2.dllとして、それぞれのディレクトリに配置します。
つまり、AddinB1.dllとAddinB2.dllは同じプロジェクトから生成されたものであり、ExportMetadataの値も同一です。これを実行すると、『Addin - B2』とメッセージ出力されます。
『Addin - B2』と出力するAddinB2.dllはA2ディレクトリ配下のDLLではないため、AddinB1.dllが利用され、『Addin - B1』と出力されることを希望します。
これはどうも、2つのプラグイン機構を持ちたく、それぞれexe直下、A2ディレクトリ直下に別な機構のプラグインを配置する想定であり、
A2ディレクトリを対象とする前にexe直下を対象として、AddinB2.dllが読み込まれてしまっているように思います。
(実際、exe直下を読み込まないようコメントアウトすると、問題が解消されます。)あまり意味ないことかもしれませんが、以下の想定があると、いつになってもプラグインの動作が修正されないことになり、
どうにかならないかと思っています。
ゴリゴリに書いてたら、そもそもexe直下のDLLは対象にしないから、何度実行しようが対象にならないわけですし。
1.A2/AddinB2.dllがもともと存在し、動作している。
2.AddinB2.dllが何かの弾みで知らない間にexe直下に配置されており、そのことに気付いていない。
3.AddinBの動作を修正し、A2/AddinB1.dllとして配置した。
4.動かしてみても、『Addin - B2』と出力され、修正が適用されない。そもそもターゲットとするディレクトリおよびインターフェースの指定で、それだけ見てくれればいいのに、DirectoryCatalog(".\", "*.dll")で先にexe直下のものを認識してしまっているのが起因のようなのですが、これって解消できるもんでしょうか??
てっきり"A2\*.dl"を行うより前に、一度CompositionContainer.Dispose()すれば"*.dll"の方は対象外になるのかなと思っていたのですが、一度やってしまうとプログラムを終了するまで有効になってしまうのでしょうか?
端的に言うと、プラグイン機構Aの対象ディレクトリ内に、プラグイン機構BのプラグインDLLを誤って配置していると、該当するプラグイン機構のものでないにも関わらず認識されてしまい、誤った動作をしてしまう、ということになります。