先简单了解一下C#下插件框架。插件一般就是定义了某个特定接口的并被动态加载的动态库。应用程序启动后,可以查找(比如某个特定目录)、动态加载、识别(某个特定接口)、使用插件(调用接口函数等)。
现在.net库中有了两套插件的框架:
MAF: Managed Add-in Framework |
从VS2008(.NET3.5)开始 |
MEF: Managed Extensibility Framework |
从VS2010(.NET4.0)开始 |
这两个的优劣,网上已经有了很多的讨论。最主要的可能就是:(fixme)
主程序 |
程序接口 |
接口 |
插件接口 |
插件 |
||
Host |
Host viewsof Add-ins |
Contracts |
Add-In views |
Add-In |
||
adapters |
adapters |
看起来好复杂啊,但似乎还不是太难理解:
凭空多出这个多东西,而且应该程序要能找到它们,故MAF都它们的位置有要求:
类别 |
位置(某个共同目录的子目录) |
HostSideAdapters |
PathOfDbzhang\HostSideAdapters |
Contracts |
PathOfDbzhang\Contracts |
AddInSideAdapters |
PathOfDbzhang\AddInSideAdapters |
AddInViews |
PathOfDbzhang\AddInViews |
AddIns |
PathOfDbzhang\AddIns\XXXX |
这个东西太麻烦了,恩,抛开VS集成环境,直接使用命令行来试试一个简单的例子。
源文件 |
==> |
最终产物 |
说明 |
Application.cs |
Application.exe |
我们的程序 |
|
HostAddInView.cs |
MyHostAddInView.dll |
||
HostSideAdapter.cs |
HostSideAdapters/ MyHostSideAdapter.dll |
||
AddInContract.cs |
Contracts/ MyAddInContract.dll |
接口 |
|
AddInSideAdapter.cs |
AddInSideAdapters/ MyAddInSideAdapter.dll |
||
AddInView.cs |
AddInViews/ MyAddInView.dll |
||
Addin1.cs |
AddIns/XXXX/Addin1.dll |
插件 |
定义程序操作的接口HostAddInView.cs :
namespace CalcHVAs { public abstract class Calculator { public abstract double Calc(double a, double b); } }
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.AddIn.Hosting; using CalcHVAs; namespace MathHost { class Program { static void Main() { // Assumes that the current directory is the addins root directory. String addInRoot = Environment.CurrentDirectory; // Search for addins. AddInStore.Update(addInRoot); Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(Calculator), addInRoot); // Select an add-in. if (tokens.Count == 0) { Console.WriteLine("No plugins available."); return; } AddInToken calcToken = ChooseCalculator(tokens); //Activate the AddIn in a new application domain with the Internet trust level. Calculator calc = calcToken.Activate<Calculator>(AddInSecurityLevel.Internet); //Run the add-in. RunCalculator(calc); } private static AddInToken ChooseCalculator(Collection<AddInToken> tokens) { Console.WriteLine("Available Plugins: "); int tokNo = 0; foreach (AddInToken tok in tokens) { Console.WriteLine("\t[{0}]: {1}", tokNo.ToString(), tok.AssemblyName); tokNo++; } 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]; } } return tokens[0]; } private static void RunCalculator(Calculator calc) { Console.Write("Input two numbers such as 2 3 (Or type <q> to exit). \n%> "); String line = Console.ReadLine(); while (!line.Equals("q")) { try { String[] parts = line.Split(' '); double a = Double.Parse(parts[0]); double b = Double.Parse(parts[1]); Console.Write("{0}\n%> ", calc.Calc(a, b)); } catch { Console.Write("Invalid command: {0} \n%> ", line); } line = Console.ReadLine(); } } } }
定义插件需要实现的接口 AddInView.cs
using System; using System.AddIn.Pipeline; namespace CalcAddInViews { [AddInBase()] public abstract class Calculator { public abstract double Calc(double a, double b); } }
using System.AddIn; using CalcAddInViews; namespace CalcAddIns { [AddIn("Dbzhang800 AddIn",Version="1.0.0.0")] public class AddInCalcV1 : Calculator { public override double Calc(double a, double b) { return a + b; } } }
定义接口 AddInContract.cs :
using System.AddIn.Contract; using System.AddIn.Pipeline; namespace CalculatorContracts { [AddInContract] public interface ICalc1Contract : IContract { double Calc(double a, double b); } }
将应用程序和接口的接口和该接口分别进行适配
HostSideAdpater.cs
using System; using System.AddIn.Pipeline; using CalcHVAs; using CalculatorContracts; namespace CalcHostSideAdapters { [HostAdapterAttribute()] public class CalculatorContractToViewHostSideAdapter : Calculator { private ICalc1Contract _contract; private System.AddIn.Pipeline.ContractHandle _handle; public CalculatorContractToViewHostSideAdapter(ICalc1Contract contract) { _contract = contract; _handle = new ContractHandle(contract); } public override double Calc(double a, double b) { return _contract.Calc(a, b); } } }
AddInSideAdapter.cs
using System; using System.AddIn.Pipeline; using CalcAddInViews; using CalculatorContracts; namespace CalcAddInSideAdapters { [AddInAdapter()] public class CalculatorViewToContractAddInSideAdapter : ContractBase, ICalc1Contract { private Calculator _view; public CalculatorViewToContractAddInSideAdapter(Calculator view) { _view = view; } public virtual double Calc(double a, double b) { return _view.Calc(a, b); } } }
前面7个文件,要分别编译成7个dll/exe文件,还好,每一只需要一个csc命令即可,写成一个.bat文件,如下
set libPath="C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5" csc /target:library /out:Contracts\MyAddInContract.dll /lib:%libPath% /r:System.AddIn.Contract.dll;System.AddIn.dll AddInContract.cs csc /target:library /out:AddInViews\MyAddInView.dll /lib:%libPath% /r:System.AddIn.dll AddInView.cs csc /target:library HostAddInView.cs csc /target:library /out:AddInSideAdapters\MyAddInSideAdpter.dll /lib:%libPath% /r:System.AddIn.dll;System.AddIn.Contract.dll;AddInViews\MyAddInView.dll;Contracts\MyAddInContract.dll AddInSideAdapter.cs csc /target:library /out:HostSideAdapters\MyHostSideAdpter.dll /lib:%libPath% /r:System.AddIn.dll;System.AddIn.Contract.dll;HostAddInView.dll;Contracts\MyAddInContract.dll HostSideAdapter.cs csc /lib:%libPath% /r:System.AddIn.dll;HostAddInView.dll Application.cs csc /target:library /out:AddIns\CalcV1\MyAddIn1.dll /lib:%libPath% /r:System.AddIn.dll;System.AddIn.Contract.dll;AddInViews\MyAddInView.dll AddIn1.cs
看看运行结果:
E:\CalculatorV1> application Available Plugins: [0]: MyAddIn1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null Which calculator do you want to use? 0 Input two numbers such as 2 3 (Or type <q> to exit). %> 2 66 68 %> 333 221 554 %> q