最近のMEF
http://blog.slaks.net/2014-11-16/mef2-roslyn-visual-studio-compatibility/
によるとMetroで使いたいという要請からMEF2が生まれた。MEF3もあるよ(まだ内部利用)。
MEF2がMEFに比べて何が嬉しいのかについては
http://blogs.microsoft.co.il/bnaya/2013/01/06/mef-20-toc/
に紹介してあった。
個々の記事が短いので読みやすいです。
それによるとGenericsできるよ。じゃぁ、MEF2だ。
しかし・・・
MEF2で見つかったサンプルは、自分でExportして自分でImportする自作自演型のものばかりで
別のdllにわけたPluginから使うにはどうするのかがなかなかわからなかった。
さらに、MEF2ですべての要素がMEFに置き換わったわけではなく、残っているけど無効にされていると思しきメソッドがあって落とし穴だらけだった。なんとか動くようになったのでメモを残します。
実装
最初に、
MEFによるC#アドインの作成を参考にMEFの動作確認をしてそこからMEF2向けに改造した。
ソリューションは、インタフェース定義のAddinContract, プラグイン実装のAddinA, プラグインをホストするMEFSampleの3プロジェクト構成。
AddinContractライブラリ
シンプル。参照の追加など何もいらない。
using System.Collections.Generic;
namespace AddinContract
{
public interface IAddinContract
{
string AddinTitle { get; }
void DoWork();
}
}
AddinAプラグインライブラリ
プラグイン実装。AddinContractライブラリを参照に加える。
MEF2の力により[Export(typeof(IAddinContract))]
が不要になったので、
MEF関連への参照を追加する必要はない。
using AddinContract;
namespace AddinA
{
public class AddinSampleA : IAddinContract
{
public string AddinTitle { get { return "Addin - A"; } }
public void DoWork() { return; }
}
}
これをビルドして、
AddinA.dllを
MEFSampleプロジェクトのbin/DEBUG/addinsディレクトリにコピーする。
MEFSampleプログラム(コンソール)
いよいよ問題のプラグインを使う方の実装です。
まず、nugetでMicrosoft.Compositionをインストールする。
PluginHostクラス
public class PluginHost
{
//[System.Composition.ImportMany]
[System.ComponentModel.Composition.ImportMany]
public List<IAddinContract> Addins
{
get;
set;
}
}
第1の罠、MEF2の属性のネームスペースが違うw
Pluginを読み込んでPluginHostに実体化させる
いくつかの工程に分かれる。
最初にMEF2のAPI上の特徴のRegistrationBuilderを作成して、必要なものをExportする。
次に、Exportされたものが入っているAssemblyからAssemblyCatalogを作る。
ここはMEFの様式だ。だけど、MEF2では使えなくなったと思しきものがある。
最後に、Containerを作って実体化させる工程。これもMEFの様式。だけど、MEF2ではw
RegistrationBuilder
RegistrationBuilderこそがMEF2の象徴。これが無いものはMEFであり、あるものはMEF2である。
こいつがAssemblyからclassやinterfaceを検索する。
なので検索できるように事前に情報をExportしてあげる必要がある。
入れるもの(Plugin, Exportする人)と入れ物(PluginのList, Importする人)の両方を登録する。
var builder = new System.ComponentModel.Composition.Registration.RegistrationBuilder();
// addins
builder
.ForTypesDerivedFrom<IAddinContract>() // 対象のインタフェースを指定
//.Export<IAddinContract>() // 個別指定。これでもOK
.ExportInterfaces()
;
// host
builder
.ForType<PluginHost>() // 対象のクラスを指定
//.ImportProperty(x => x.Addins, b => b.AsMany()) // 入れ物のプロパティを指定。ImportMany属性かこれかどっちかでいい
.Export<PluginHost>()
;
AssemblyCatalog
ここの情報が見つからなくて苦労。
MEF2ではDirectoryCatalogは死んだ。ぽい。
var hostCatalog = new System.ComponentModel.Composition.Hosting.AssemblyCatalog(typeof(PluginHost).Assembly, builder);
var aCataog = new System.ComponentModel.Composition.Hosting.AggregateCatalog(hostCatalog);
#if false
var catalog = new System.ComponentModel.Composition.Hosting.DirectoryCatalog("addins");
aCataog.Catalogs.Add(catalog);
#else
foreach (var f in new DirectoryInfo("addins").GetFiles().Where(x => x.Name.ToLower().EndsWith(".dll")))
{
var catalog = new System.ComponentModel.Composition.Hosting.AssemblyCatalog(Assembly.LoadFile(f.FullName), builder);
aCataog.Catalogs.Add(catalog);
}
#endif
CompositionContainer
MEF2ではComposePartsは死んだ。ぽい
var container = new System.ComponentModel.Composition.Hosting.CompositionContainer(aCataog);
#if false
var host = new PluginHost();
container.ComposeParts(host);
return host;
#else
return container.GetExportedValue<PluginHost>();
#endif
メモ
MEE2システムには、3つの役割がある。
プラグイン、プラグインのホスト、プラグインを使うプログラムである。
参照関係は大雑把ににこんな感じ。
プラグインの方は特定の
interfaceを実装する以外に気にすることはない。
本体の方は、忘れずにpluginhostの情報をAssemblyCatalogに追加するべし。
+-- Exe ----------------+--> Microsoft.Composition
| |
| +----+---> pluginhost |
| |User| |--> IPlugin.dll
| +----+ | ^
| | |
+-----------------------+ |
|
+-- Plugin.dll --+------------+
| Plugin |
+----------------+
まとめ
- MEF2ではGenericsが使える
- MEF2では属性なしで、コードで指定することができる
- MEF2でも属性は使えるが、同じ名前でMEF2のnamespaceにある属性を使うべし
- AssemblyCatalogはAggregateCatalogに全部放り込む
以上、MEF2のメモでした。