我设想的可扩展结构(插件) (三)
背景:
第一部分见:我设想的可扩展结构(插件) (ㄧ)
第二部份见:我设想的可扩展结构(插件) (二)
而本文将对此插件结构的设计和关键代码作一个介绍。
设计
使用Rose画的设计图(去掉了一些辅助类)The Package Diagram :
The Plug-in Interface Class Diagram:
The Plug-in Manager Class Diagram:
对关键类的说明
1.IPlugin──任何插件需要实现的接口。放在单独的Project中,以便于其它Project引用。Execute方法执行动作,Dispose方法则终止执行且释放资源。
2.IAppContext──插件以此来了解其运行环境。PluginManager将此类型的对象传递给IPlugin的Execute方法。
3.PluginAssemblyAttribute──描述插件程序集的属性,比如此插件所从属的模块,插件类的名字。
4.PluginDependencyAttribute──描述插件类的属性,指明所依赖的其它插件。
5.PluginManager──插件管理类,继承MarshalByRefObject类。负责加载,执行,停止和删除插件。为每个模块的插件创建一个AppDomain,以按照模块来隔离插件。
6.PluginDescriptor──插件的包装类,负责将按照插件之间的依赖关系进行排序,以便于PluginManager按照正确的顺序加载插件。
7.PluginSecurityManager──插件安全管理类。负责检查插件的合法性,设置插件的执行权限。
8.PluginProvider──插件提供者,继承MarshalByRefObject类。负责提供需要加载的插件的Assembly的列表。具体实现中可以读取配置找到插件,也可以自动发现插件。原本我设计了一个工厂,但由于我只是采用自动查找插件的方式,所以为了简化设计,就把这个工厂去掉了。PluginProvider创建一个临时的AppDomain来加载插件目录下的程序集,并读取其属性以检验此程序集中是否包含插件;返回包含插件的程序集的列表,最后卸载此临时AppDomain。
9.DirectoryMonitor──监控部署插件的目录,一旦有新增加的插件,或者有新版本插件被部署后,就能立刻通知PluginManager进行处理。具体实现中通常使用代理了处理了。此类继承FileSystemWatcher类,使用中需要注意一些问题──同一个事件可能重复触发;一个文件拷贝动作会触发多个事件。
10. SoftwareDelivery──这个子系统负责进行系统部署,远程将插件部署到指定的各个客户端的某目录下。
11. 还有一些辅助类,比如读取Attribute的类,还有一些包装类,在图中没有画出。
代码
这里只对关键的代码作一个说明。
1. 文件监控:
public delegate void ChangePluginDelegate(string assemblyString);
public class DirectoryMonitor : FileSystemWatcher
{
private AddPluginDelegate addPluginDelegate;
private ChangePluginDelegate changePluginDelegate;
public DirectoryMonitor(string monitorDirectory, AddPluginDelegate addPluginDelegate, ChangePluginDelegate changePluginDelegate)
{
this.addPluginDelegate = addPluginDelegate;
this.changePluginDelegate = changePluginDelegate;
this.Path=monitorDirectory;
this.Filter="*.dll";
this.IncludeSubdirectories=true;
this.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Attributes;
this.Changed += new FileSystemEventHandler(fsWatcher_Changed);
this.Created += new FileSystemEventHandler(fsWatcher_Created);
}
//move the files to another directory
// in fsWatcher_Changed and fsWatcher_Created method
}
2. 寻找插件:
{
public PluginProvider()
{
}
public PluginProvider(string shadowCopyDir)
{
this.shadowCopyDir=shadowCopyDir;
}
private string shadowCopyDir;
public PluginAssemblyDescriptor[] GetPluginAssemblies()
{
string[] assemblies=null;
PluginAssemblyDescriptor[] ret=null;
AppDomainSetup setupInfo=new AppDomainSetup();
setupInfo.ApplicationBase=AppDomain.CurrentDomain.BaseDirectory+"\\"+shadowCopyDir;
AppDomain tempDomain=AppDomain.CreateDomain("",null,setupInfo);
PluginProvider afpp=null;
try
{
afpp=(PluginProvider)tempDomain.CreateInstanceFromAndUnwrap(
"XXX.dll","XXX.PluginProvider");
DirectoryInfo pluginShadowDir=new DirectoryInfo(tempDomain.BaseDirectory);
FileInfo[] assemblyFiles=pluginShadowDir.GetFiles("*.dll");
if(assemblyFiles==null || assemblyFiles.Length==0)
return null;
assemblies=new string[assemblyFiles.Length];
int i=0;
foreach(FileInfo assemblyFile in assemblyFiles)
{
assemblies[i++]=assemblyFile.Name.Replace(assemblyFile.Extension,"");
}
PluginAssemblyDescriptor[] temp=new PluginAssemblyDescriptor[assemblies.Length];
int count=0;
for(i=0;i<assemblies.Length;i++)
{
PluginAssemblyDescriptor pad=(PluginAssemblyDescriptor)afpp.LoadAssembly(assemblies[i]);
if(pad!=null)
temp[count++]=pad;
}
if(count!=0)
{
ret=new PluginAssemblyDescriptor[count];
for(i=0;i<count;i++)
{
ret[i]=new PluginAssemblyDescriptor();
ret[i].AssemblyString=temp[i].GetAssemblyString();
ret[i].PluginAssemblyAttribute=temp[i].GetPluginAssemblyAttribute();
}
}
}
catch(Exception e)
{
//do something
}
finally
{
AppDomain.Unload(tempDomain);
}
return ret;
}
public PluginAssemblyDescriptor GetPluginAssembly(string assemblyString)
{
//get one a special assembly
}
private PluginAssemblyDescriptor LoadAssembly(string assemblyString)
{
Assembly assembly=AppDomain.CurrentDomain.Load(assemblyString);
AssemblyAttributeReader aar=new AssemblyAttributeReader(assembly);
PluginAssemblyAttribute paa=aar.GetPluginAssemblyAttribute();
if(paa==null)
return null;
PluginAssemblyDescriptor pad=new PluginAssemblyDescriptor(assemblyString,paa);
return pad;
}
}
3. 加载并且执行插件:
和上面寻找插件的过程类似,需要为每个模块创建应用程序域。PluginManager在各个应用程序域中创建PluginManager的实例,加载插件,创建插件的实例,并且调用Execute方法。说白了,就是PluginManager在新的应用程序域中再创建一个自己,这些PluginManager对象管理本域中的插件。代码太长,在这里就不贴出了。
后记
这个插件结构已经完成了,可能和大家常见的UI中的插件有些不同。我的这个插件结构只是适合于我在我设想的可扩展结构(插件) (ㄧ)提到的场合,而不是一个通用的插件结构库。希望大家积极提出批评意见!
今天是个特殊的日子(7月7日),发此文亦当纪念。