插件化编程实现的一份糖炒栗子~~
Posted 易墨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了插件化编程实现的一份糖炒栗子~~相关的知识,希望对你有一定的参考价值。
迷茫的原因是因为想得太多,做得太少。因为只是 想 真的很容易,转瞬之间就会产生无数个念头,或许是该做点什么了吧。
但是整个人都是懒的,是废的,是大脑控制不住自己的行为的。解决方案唯有一步一步的去把行为变成习惯。
坚持一件事挺不容易的,不论结果的好坏,过程中总有收获的,坚持,不会是一件坏事。
胡言乱语结束~~~
下面是记录分享的一点东西~~请笑纳
0.结构一览
1.定义插件接口
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YimoCore { public interface IPlugin { /// <summary> /// Gets or sets the plugin descriptor /// </summary> PluginDescriptor PluginDescriptor { get; set; } /// <summary> /// Install plugin /// </summary> void Install(); /// <summary> /// Uninstall plugin /// </summary> void Uninstall(); } }
2.添加插件信息类
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace YimoCore { /// <summary> /// 插件信息 /// </summary> public sealed class PluginDescriptor { /// <summary> /// 插件主目录 /// </summary> public string PluginFileName { get; set; } /// <summary> /// 插件类型 /// </summary> public Type PluginType { get; set; } /// <summary> /// 插件主程序集 /// </summary> public Assembly ReferencedAssembly { get; internal set; } /// <summary> /// 原始程序集文件 /// </summary> public FileInfo OriginalAssemblyFile { get; internal set; } /// <summary> /// 插件包目录 /// </summary> public FileInfo PluginConfigFile { get; internal set; } /// <summary> /// 类别 /// </summary> public string Group { get; set; } /// <summary> /// 插件名称 /// </summary> public string FriendlyName { get; set; } /// <summary> /// 程序集名称 /// </summary> public string SystemName { get; set; } /// <summary> /// 描述 /// </summary> public string Description { get; set; } /// <summary> /// 版本号 /// </summary> public string Version { get; set; } /// <summary> /// 支持版本 /// </summary> public IList<string> SupportedVersions { get; set; } /// <summary> /// 作者 /// </summary> public string Author { get; set; } /// <summary> /// 排序 /// </summary> public int DisplayOrder { get; set; } /// <summary> /// 获取插件实例 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> public T Instance<T>() where T : class, IPlugin { var instance = Activator.CreateInstance(PluginType) as T; if (instance != null) instance.PluginDescriptor = this; return instance; } public IPlugin Instance() { return Instance<IPlugin>(); } public int CompareTo(PluginDescriptor other) { if (DisplayOrder != other.DisplayOrder) return DisplayOrder.CompareTo(other.DisplayOrder); else return System.String.Compare(FriendlyName, other.FriendlyName, System.StringComparison.Ordinal); } public override string ToString() { return FriendlyName; } public override bool Equals(object obj) { var other = obj as PluginDescriptor; return other != null && SystemName != null && SystemName.Equals(other.SystemName); } public override int GetHashCode() { return SystemName.GetHashCode(); } } }
3.插件读取
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Web; namespace YimoCore { public class PluginManager { private const string PluginsPath = "/modules"; //插件目录 private const string ShadowCopyPath = "/modules/bin"; //插件影子目录 public static IEnumerable<PluginDescriptor> ReferencedPlugins { get; set; } public static void Init() { var appdir = AppDomain.CurrentDomain.BaseDirectory.Substring(0, AppDomain.CurrentDomain.BaseDirectory.LastIndexOf("\\\\")); appdir = appdir.Substring(0, appdir.LastIndexOf("\\\\")); appdir = appdir.Substring(0, appdir.LastIndexOf("\\\\")); //插件目录 var pluginFolder = new DirectoryInfo(appdir + PluginsPath); //插件bin目录 var shadowFolder = new DirectoryInfo(appdir + ShadowCopyPath); var referencedPlugins = new List<PluginDescriptor>(); try { pluginFolder.Create(); shadowFolder.Create(); //清空bin目录 foreach (var fileInfo in shadowFolder.GetFiles()) { fileInfo.Delete(); } var pluginConfigFiles = pluginFolder.GetFiles("about.xml", SearchOption.AllDirectories); foreach (var pluginConfigFile in pluginConfigFiles) { //获取插件信息 var pluginDescriptor = PluginFileParser.ParsePluginDescriptionFile(pluginConfigFile.FullName); try { if (pluginConfigFile.Directory == null) continue; //获取插件所有的dll var pluginFiles = pluginConfigFile.Directory.GetFiles("*.dll", SearchOption.AllDirectories); var mainPluginFile = pluginFiles.FirstOrDefault( item => item.Name.Equals(pluginDescriptor.PluginFileName, StringComparison.InvariantCultureIgnoreCase)); pluginDescriptor.PluginConfigFile = pluginConfigFile; pluginDescriptor.OriginalAssemblyFile = mainPluginFile; pluginDescriptor.ReferencedAssembly = DeployDllFile(mainPluginFile, shadowFolder); foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(t)) { if (t.IsInterface == false && t.IsClass && !t.IsAbstract) { pluginDescriptor.PluginType = t; break; } } } referencedPlugins.Add(pluginDescriptor); } catch (ReflectionTypeLoadException ex) { throw; } } } catch (ReflectionTypeLoadException ex) { } ReferencedPlugins = referencedPlugins; } /// <summary> /// 部署程序集 /// </summary> /// <param name="dllFile">插件程序集文件</param> /// <param name="shadowFolder">/Plugins/bin目录</param> private static Assembly DeployDllFile(FileInfo dllFile, DirectoryInfo shadowFolder) { DirectoryInfo copyFolder; //根据当前的信任级别设置复制目录 copyFolder = shadowFolder; var newDllFile = new FileInfo(copyFolder.FullName + "\\\\" + dllFile.Name); try { File.Copy(dllFile.FullName, newDllFile.FullName, true); } catch (Exception ex1)//在某些情况下会出现"正由另一进程使用,因此该进程无法访问该文件"错误,所以先重命名再复制 { try { File.Move(newDllFile.FullName, newDllFile.FullName + Guid.NewGuid().ToString("N") + ".locked"); } catch (Exception ex2) { throw ex2; } File.Copy(dllFile.FullName, newDllFile.FullName, true); } var assembly = Assembly.LoadFrom(newDllFile.FullName); //将程序集添加到当前应用程序域 //BuildManager.AddReferencedAssembly(assembly); return assembly; } } }
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml.Linq; namespace YimoCore { /// <summary> /// Plugin files parser /// </summary> public static class PluginFileParser { public static IList<string> ParseInstalledPluginsFile(string filePath) { if (!File.Exists(filePath)) return new List<string>(); var text = File.ReadAllText(filePath); if (String.IsNullOrEmpty(text)) return new List<string>(); var lines = new List<string>(); using (var reader = new StringReader(text)) { string str; while ((str = reader.ReadLine()) != null) { if (String.IsNullOrWhiteSpace(str)) continue; lines.Add(str.Trim()); } } return lines; } public static void SaveInstalledPluginsFile(IList<String> pluginSystemNames, string filePath) { if (pluginSystemNames == null || pluginSystemNames.Count == 0) return; string result = ""; foreach (var sn in pluginSystemNames) result += string.Format("{0}{1}", sn, Environment.NewLine); File.WriteAllText(filePath, result); } public static PluginDescriptor ParsePluginDescriptionFile(string filePath) { XDocument doc; try { doc = XDocument.Load(filePath); } catch (Exception) { return null; } var pluginEle = doc.Element("plugin"); if (pluginEle == null) return null; var descriptor = new PluginDescriptor(); var ele = pluginEle.Element("SystemName"); if (ele != null) descriptor.SystemName = ele.Value; ele = pluginEle.Element("Group"); if (ele != null) descriptor.Group = ele.Value; ele = pluginEle.Element("FriendlyName"); if (ele != null) descriptor.FriendlyName = ele.Value; ele = pluginEle.Element("Description"); if (ele != null) descriptor.Description = ele.Value; ele = pluginEle.Element("Version"); if (ele != null) descriptor.Version = ele.Value; ele = pluginEle.Element("SupportedVersions"); if (ele != null) { //parse supported versions descriptor.SupportedVersions = ele.Value.Split(new[] { \',\' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => x.Trim()) .ToList(); } ele = pluginEle.Element("Author"); if (ele != null) descriptor.Author = ele.Value; ele = pluginEle.Element("DisplayOrder"); if (ele != null) { int displayOrder; int.TryParse(ele.Value, out displayOrder); descriptor.DisplayOrder = displayOrder; } ele = pluginEle.Element("FileName"); if (ele != null) descriptor.PluginFileName = ele.Value; if (descriptor.SupportedVersions.Count == 0) descriptor.SupportedVersions.Add("2.00"); return descriptor; } public static void SavePluginDescriptionFile(PluginDescriptor plugin) { if (plugin == null) throw new ArgumentException("plugin"); if (plugin.PluginConfigFile == null) throw new Exception(string.Format("没有加载插件 {0} 的配置文件", plugin.SystemName)); var doc = new XDocument( new XDeclaration("1.0", "utf-8", "yes"), new XElement("Group", plugin.Group), new XElement("FriendlyName", plugin.FriendlyName), new XElement("SystemName", plugin.SystemName), new XElement("Description", plugin.Description), new XElement("Version", plugin.Version), new XElement("SupportedVersions", string.Join(",", plugin.SupportedVersions)), new XElement("Author", plugin.Author), new XElement("DisplayOrder", plugin.DisplayOrder), new XElement("FileName", plugin.PluginFileName) ); doc.Save(plugin.PluginConfigFile.FullName); } } }
4.使用
4.0新建插件接口并继承IPlugin
4.1:为插件类库添加about.xml文件配置插件信息
<?xml version="1.0" encoding="utf-8" ?> <plugin> <Group>Sign</Group> <FriendlyName>素材网</FriendlyName> <SystemName>素材网</SystemName> <Description>素材网签到</Description> <Version>1.0</Version> <SupportedVersions>1.0</SupportedVersions> <Author>YiMo</Author> <DisplayOrder>1</DisplayOrder> <FileName>素材网.dll</FileName> </plugin>
4.2在插件类库中实现ISign接口 (这里需要将PluginDescriptor字段实现)
4.3:修改插件所在类库的生成事件 工具下载
生成......
4.4:生成解决方案后,就可以愉快的使用了
最后附上使用上述方式写的的一个签到程序:https://github.com/yimogit/YimoCustomizedSign
现已经实现三个网站的的一键签到。后续会实现更多网站的签到功能等~~
跪求路过的大牛指点一二,也极度希望有人能和我一起来探讨,优化,扩展这个小软件和 更多更多更多的想法。
无论如何,每天都要进步一点点,基础再牢一点,效率再高一点,代码再风骚一点,生活再开心一点。每天只要一点点就好。
以上是关于插件化编程实现的一份糖炒栗子~~的主要内容,如果未能解决你的问题,请参考以下文章
Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段