MEF和MAF都是C#下的插件編程框架,我們通過它們只需簡單的配置下源代碼就能輕松的實現插件編程概念,設計出可擴展的程序。這真是件美妙的事情!
MEF(Managed Extensibility Framework)
MEF的工作原理大概是這樣的:首先定義一個接口,用這個接口來約束插件需要具備的職責;然後在實現接口的程序方法上面添加反射標記“[Export()]”將實現的內容導出;最後在接口的調用程序中通過屬性將插件加載進來。我們還是用代碼來描述吧:
1. 定義一個接口:
/* 作者:GhostBear 博客:http://blog.csdn.net/ghostbear 簡介:該節主要學習.net下的插件編程框架MEF(managed extensibility framework) */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; namespace chapter28_simplecontract { public interface ICalculator { IList<IOperation> GetOperations(); double Operate(IOperation operation, double[] operands); } public interface IOperation { string Name { get; } int NumberOperands { get; } } public interface ICaculatorExtension { string Title { get; } string Description { get; } FrameworkElement GetUI(); } }
2. 實現定義的接口(部分一)
[Export(typeof(ICalculator))] public class Caculator:ICalculator { public IList<IOperation> GetOperations() { return new List<IOperation>(){ new Operation{ Name="+",NumberOperands=2}, new Operation{Name="-",NumberOperands=2}, new Operation{Name="*",NumberOperands=2}, new Operation{Name="/",NumberOperands=2} }; } public double Operate(IOperation operation, double[] operands) { double result=0; switch (operation.Name) { case "+": result = operands[0] + operands[1]; break; case "-": result = operands[0] - operands[1]; break; case "*": result = operands[0] * operands[1]; break; case "/": result = operands[0] / operands[1]; break; default: throw new Exception("not provide this method"); } return result; } } public class Operation:IOperation { public string Name { get; internal set; } public int NumberOperands { get; internal set; } }
實現定義的接口(部分二)
[Export(typeof(ICalculator))] public class Caculator : ICalculator { public IList<IOperation> GetOperations() { return new List<IOperation>(){ new Operation{ Name="+",NumberOperands=2}, new Operation{Name="-",NumberOperands=2}, new Operation{Name="*",NumberOperands=2}, new Operation{Name="/",NumberOperands=2}, new Operation{Name="%",NumberOperands=2}, new Operation{Name="**",NumberOperands=1}, }; } public double Operate(IOperation operation, double[] operands) { double result = 0; switch (operation.Name) { case "+": result = operands[0] + operands[1]; break; case "-": result = operands[0] - operands[1]; break; case "*": result = operands[0] * operands[1]; break; case "/": result = operands[0] / operands[1]; break; case "%": result=operands[0]%operands[1]; break; case "**": result=operands[0]*operands[0]; break; default: throw new Exception("not provide this method"); } return result; } } public class Operation : IOperation { public string Name { get; internal set; } public int NumberOperands { get; internal set; } }
分析:
標記“[Export(typeof(ICalculator))]”聲明表達的意思是:這個類可以編譯為插件,並能放入插件容器“ICalculator”中。這裡需要注意的是:部分一和部分二的代碼分布在不同的程序集中。導出的插件不一定必須是以類的形式,也可以是方法。
通過導出方法來生成插件:
public class Bomb { [Export("Bomb")] public void Fire() { Console.WriteLine("you are dead!!!"); } }
插件的調用者:
* 簡介:該節主要學習.net下的插件編程框架MEF(managed extensibility framework) */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; using chapter28_simplecontract; namespace chapter28 { class Program { [ImportMany(typeof(ICalculator))] public IEnumerable<ICalculator> Calculators { get; set; } [Import("Bomb")] public Action Bomb { get; set; } static void Main(string[] args) { Program pro = new Program(); pro.Run(); pro.Run2(); } public void Run() { var catalog = new DirectoryCatalog("c:\\plugins"); var container = new CompositionContainer(catalog); try { container.ComposeParts(this); } catch (Exception ex) { Console.WriteLine(ex.Message); return; } ICalculator myCalculator = Calculators.ToList<ICalculator>()[1]; var operations = myCalculator.GetOperations(); var operationsDict = new SortedList<string, IOperation>(); foreach(IOperation item in operations) { Console.WriteLine("Name:{0},number operands:{1}", item.Name, item.NumberOperands); operationsDict.Add(item.Name, item); } Console.WriteLine(); string selectedOp = null; do { try { Console.Write("Operation?"); selectedOp = Console.ReadLine(); if (selectedOp.ToLower() == "exit" || !operationsDict.ContainsKey(selectedOp)) { continue; } var operation = operationsDict[selectedOp]; double[] operands = new double[operation.NumberOperands]; for (int i = 0; i < operation.NumberOperands; i++) { Console.WriteLine("\t operand {0}?", i + 1); string selectedOperand = Console.ReadLine(); operands[i] = double.Parse(selectedOperand); } Console.WriteLine("calling calculator"); double result = myCalculator.Operate(operation, operands); Console.WriteLine("result:{0}", result); } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(); continue; } } while (selectedOp != "exit"); } public void Run2() { var catalog = new DirectoryCatalog("c:\\plugins"); var container = new CompositionContainer(catalog); container.ComposeParts(this); Bomb.Invoke(); Console.ReadKey(); } } }
分析:
標記“[ImportMany(typeof(ICalculator))]”,該聲明表達的意圖是:將所有聲明了標記“[Export(typeof(ICalculator))]”的程序集加載進容器。這裡“[ImportMany]和”[Import]”的區別就是:前者的容器可以存放多個插件,而後者只能存放一個。
光聲明“[Import()]”和”[Export()]”標記是不行的,還必須通過下面的代碼將這兩個標記的功能聯合起來:
//DirectoryCatalog表示這類插件會存放在系統的哪個文件夾下 var catalog = new DirectoryCatalog("c:\\plugins"); var container = new CompositionContainer(catalog); try { //將存放在目錄中的插件按“[Export()]和[Import()]”規則裝載進當前 //類中。 container.ComposeParts(this); } catch (Exception ex) { Console.WriteLine(ex.Message); return; }
執行結果
Name:+,number operands:2 Name:-,number operands:2 Name:*,number operands:2 Name:/,number operands:2 Operation?+ operand 1? 1 operand 2? 1 calling calculator result:2 Operation?exit you are dead!!!
MAF(Managed Addin Framework)
MAF也是.Net為我們提供的一個“插件編程”解決方案。它比MEF復雜,需要配置很多元素。但它也有些優點:1.宿主程序和插件程序可以進行隔離,以此降低運行插件所帶來的風險;2。MAF的設計是基於7個程序集組成的管道,這些管道部分可以單獨更換,這些管道的詳細情況見下圖。
圖1
使用MAF是需要有些細節需要注意:組成管道的7個程序集在系統中的保存路徑有格式要求,並且沒個保存它的文件夾內只運行同時出現一個程序集。具體情況如下圖所示:
圖2
圖3
圖4
圖5
下面我們來看一個小Demo吧,這個demo一共有7個項目,它們分別對應圖1描述的管道中的7個部分。具體情況見下圖。
圖6
插件:Addin_1,Addin_2
插件視圖:AddinSideView
插件適配器:AddinSideAdapter
協定:IContract
宿主視圖:HostSideView
宿主適配器:HostSideAdapter
宿主程序:Host
程序代碼
Addin_1
*/ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn; using System.AddIn.Pipeline; namespace Addin_1 { [AddIn("Helloworld",Description="this is helloworld addin" ,Publisher="GhostBear",Version="1.0")] public class Addin_1:AddinSideView.AddinSideView { public string Say() { return "Helloworld"; } } }
Addin_2
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn; namespace Addin_2 { [AddIn("SuperBomb",Description="This is a bigger bomb" ,Publisher="SuperWorker",Version="1.0.0.0")] public class Addin_2:AddinSideView.AddinSideView { public string Say() { return "B--O--M--B"; } } }
AddinSideView
* 簡介:測試MAF,這段代碼是定義插件端的視圖類,該視圖類的方法和屬性必須與協定一致。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn.Pipeline; namespace AddinSideView { [AddInBase()] public interface AddinSideView { string Say(); } }
AddinSideAdapter
* 簡介:測試MAF,這段代碼是插件端的適配器類,它用來實現插件端視圖類。 * 並組合協定。這樣就能讓插件和協定解耦,如果插件有所修改就換掉 * 該適配器類就可以了。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn.Pipeline; namespace AddinSideAdapter { [AddInAdapter] public class AddinSideAdapter : ContractBase,IContract.IMyContract { private AddinSideView.AddinSideView _handler; public AddinSideAdapter(AddinSideView.AddinSideView handler) { this._handler = handler; } public string Say() { return this._handler.Say(); } } }
IContract
簡介:測試MAF,這段代碼是定義協定。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn.Pipeline; using System.AddIn.Contract; namespace IContract { [AddInContract] public interface IMyContract:System.AddIn.Contract.IContract { string Say(); } }
HostSideView
簡介:測試MAF,這段代碼用來定義宿主段的視圖類,該類的所有方法和屬性需與協定類一致。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace HostSideView { public interface HostSideView { string Say(); } }
HostSideAdapter
簡介:測試MAF,這段代碼用來定義宿主端的適配器類。該類實現宿主端的 視圖類並組合協定。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.AddIn.Pipeline; namespace HostSideAdapter { [HostAdapter()] public class HostSideAdapter:HostSideView.HostSideView { private IContract.IMyContract _contract; //這行代碼重要 private System.AddIn.Pipeline.ContractHandle _handle; public HostSideAdapter(IContract.IMyContract contract) { this._contract = contract; this._handle = new ContractHandle(contract); } public string Say() { return this._contract.Say(); } } }
Host
簡介:測試MAF,這段代碼是宿主程序。該程序可以針對保存在某個目錄下的插件來進行選擇性調用。 */ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections; using System.Collections.ObjectModel; using System.AddIn.Hosting; using HostSideView; namespace Host { class Program { static void Main(string[] args) { string path = @"D:\學習文檔\c#\c#高級編程7\MAF\MAF"; string[] warnings = AddInStore.Update(path); foreach (var tmp in warnings) { Console.WriteLine(tmp); } //發現 var tokens = AddInStore.FindAddIns(typeof(HostSideView.HostSideView), path); Console.WriteLine("當前共有{0}個插件可以選擇。它們分別為:",tokens.Count); var index = 1; foreach (var tmp in tokens) { Console.WriteLine(string.Format("[{4}]名稱:{0},描述:{1},版本:{2},發布者:{3}", tmp.Name, tmp.Description, tmp.Version, tmp.Publisher,index++)); } var token = ChooseCalculator(tokens); //隔離和激活插件 AddInProcess process=new AddInProcess(Platform.X64); process.Start(); var addin = token.Activate<HostSideView.HostSideView>(process, AddInSecurityLevel.FullTrust); Console.WriteLine("PID:{0}",process.ProcessId); //調用插件 Console.WriteLine(addin.Say()); Console.ReadKey(); } private static AddInToken ChooseCalculator(Collection<AddInToken> tokens) { if (tokens.Count == 0) { Console.WriteLine("No calculators are available"); return null; } Console.WriteLine("Available Calculators: "); // Show the token properties for each token in the AddInToken collection // (tokens), preceded by the add-in number in [] brackets. int tokNumber = 1; foreach (AddInToken tok in tokens) { Console.WriteLine(String.Format("\t[{0}]: {1} - {2}\n\t{3}\n\t\t {4}\n\t\t {5} - {6}", tokNumber.ToString(), tok.Name, tok.AddInFullName, tok.AssemblyName, tok.Description, tok.Version, tok.Publisher)); tokNumber++; } Console.WriteLine("Which calculator do you want to use?"); String line = Console.ReadLine(); int selection; if (Int32.TryParse(line, out selection)) { if (selection <= tokens.Count) { return tokens[selection - 1]; } } Console.WriteLine("Invalid selection: {0}. Please choose again.", line); return ChooseCalculator(tokens); } } }
分析
在上面的7個程序集,起解耦作用的關鍵還是2個適配器類。調用程序不直接調用協定,而是通過通過調用這2個適配器來間接調用協定。
小結
MEF和MAF為我們實現“插件編程”提供了2中選擇,它們設計的出發點也是完全不同的。在使用它們的時候還是需要更加具體需求來權衡使用。