ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

c# 支持热插拔的插件

2022-01-29 16:31:49  阅读:165  来源: 互联网

标签:插件 Console string c# 热插拔 protocolInfo WriteLine var


场景:

  这项目用到了插件化开发,不是我做的,趁着现在有空学习一下。插件就是dll,主程序可以调用dll中的方法,插件之前没有关系,耦合性低。同时便于扩展和移除。今天在家,就研究一下c#的插件开发。热插拔,就是可以在运行时进行插件的添加,删除,修改等,无需停止程序。

实现:

  1.插件化

    1.1 首先先定义一个接口:接口中是每个插件都要实现的函数,或者属性。这里我就一个获取插件信息的方法。继承Disposeable是为了移除插件时做的内存释放操作。

public interface IPlugin : IDisposable
    {
        PluginInfo GetPluginInformation();
    }

    PluginInfo类定义如下:

public class PluginInfo
    {
        public string Name { set; get; }

        public string Version { set; get; }

        public string Author { set; get; }

        public DateTime LastTime { set; get; }
    }

    1.2 然后写另一个项目,和这个插件的项目放在同一个解决方法中,作为插件端。内容:

public class Plugin_Chen : IPlugin
    {
        /// <summary>
        /// 获取插件信息
        /// </summary>
        /// <returns></returns>
        public PluginInfo GetPluginInformation()
        {
            return new PluginInfo()
            {
                Author = "Test",
                Name = "测试插件",
                Version = "V1.3.0",
                LastTime = DateTime.Now
            };
        }

        void IDisposable.Dispose()
        {
            Console.WriteLine("释放内存");
        }
    }

    这个插件端的整体架构:

 

 

     1.3 然后写主程序端,也就是加载和应用插件的程序。首先主程序端要有IPlugin这个接口的定义,如下

 

 

     1.4 然后在另一个项目的main函数中,做插件的加载和初始化(Init函数)。先从指定文件路径下读取dll文件,再从dll中读取出程序集,指定其中一个type验证是否实现了插件接口,实现了,就可以实例化接口,从而调用接口下的各个方法。

    代码如下:

class Program
    {
        /// <summary>
        /// 当前拥有的插件
        /// </summary>
        static Dictionary<string, IPlugin> _IPlugins = new Dictionary<string, IPlugin>();
        /// <summary>
        /// 当前拥有的插件信息
        /// </summary>
        static Dictionary<string, PluginInfo> _IPluginInfos = new Dictionary<string, PluginInfo>();
        /// <summary>
        /// 文件监听
        /// </summary>
        static FileListenerServer _fileListener = null;
        static void Main(string[] args)
        {
            Console.WriteLine("可插拔插件服务");
            var dic = Directory.GetCurrentDirectory();
            var path = Path.Combine(dic, "plugIn");
            Init(path);
            // 监听文件下插件变化,实现热插拔
            _fileListener = new FileListenerServer(path,ref _IPlugins,ref _IPluginInfos);
            _fileListener.Start();
            Console.WriteLine("按q/Q退出");
            while ( true )
            {
                string input = Console.ReadLine();
                switch ( input )
                {
                    case "q":
                        _fileListener.Stop();
                        return;
                    case "Q":
                        _fileListener.Stop();
                        return;
                    default:
                        Console.WriteLine("按q/Q退出");
                        break;
                }
            }
        }
        /// <summary>
        /// 初始化插件
        /// </summary>
        static void Init(string path)
        {
            Console.WriteLine(string.Format("==========【{0}】==========", "开始加载插件"));
            // 1.获取文件夹下所有dll文件
            DirectoryInfo directoryInfo = new DirectoryInfo(path);
            var dlls = directoryInfo.GetFiles();
            // 2.启动每个dll文件
            for ( int i = 0; i < dlls.Length; i++ )
            {
                // 2.1 获取程序集
                var fileData = File.ReadAllBytes(dlls[i].FullName);
                Assembly asm = Assembly.Load(fileData);
                var manifestModuleName = asm.ManifestModule.ScopeName;
                // 2.2 dll名称
                var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf("."));
                Type type = asm.GetType("Plugin_Test" + "." + classLibrayName);
                if ( !typeof(IPlugin).IsAssignableFrom(type) )
                {
                    Console.WriteLine("未继承插件接口");
                    continue;
                }
                //dll实例化
                var instance = Activator.CreateInstance(type) as IPlugin;
                var protocolInfo = instance.GetPluginInformation();
                protocolInfo.LastTime = dlls[i].LastWriteTime;
                Console.WriteLine($"插件名称:{protocolInfo.Name}");
                Console.WriteLine($"插件版本:{protocolInfo.Version}");
                Console.WriteLine($"插件作者:{protocolInfo.Author}");
                Console.WriteLine($"插件时间:{protocolInfo.LastTime}");
                _IPlugins.Add(classLibrayName, instance);
                _IPluginInfos.Add(classLibrayName, protocolInfo);
                //释放插件资源
                instance.Dispose();
                instance = null;
            }

            Console.WriteLine(string.Format("==========【{0}】==========", "插件加载完成"));
            Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _IPlugins.Count);
        }

    }

    注意,建议用var fileData = File.ReadAllBytes(dlls[i].FullName);Assembly asm = Assembly.Load(fileData);来获取程序集,不然用其他方法容易实例化出错。原因还是不清楚。

  2.热插拔

    2.1 这里主要用到了一个文件监控的类:FileSystemWatcher,在这基础上包装了一层。它可以对指定文件夹进行文件/文件夹的添加,删除,修改等操作的监控。代码如下:

public class FileListenerServer
    {
        /// <summary>
        /// 文件监听
        /// </summary>
        private FileSystemWatcher _watcher;
        /// <summary>
        /// 插件
        /// </summary>
        private Dictionary<string, IPlugin> _iPlugin;
        /// <summary>
        /// 插件信息
        /// </summary>
        private Dictionary<string, PluginInfo> _iPluginInfos = new Dictionary<string, PluginInfo>();
        public FileListenerServer(string path,ref Dictionary<string, IPlugin> keyValuePairs,ref Dictionary<string, PluginInfo> keyValues)
        {

            try
            {
                _iPluginInfos = keyValues;
                _iPlugin = keyValuePairs;
                this._watcher = new FileSystemWatcher();
                _watcher.Path = path;
                _watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.DirectoryName;
                //_watcher.IncludeSubdirectories = true;
                _watcher.Created += new FileSystemEventHandler(FileWatcher_Created);
                _watcher.Changed += new FileSystemEventHandler(FileWatcher_Changed);
                _watcher.Deleted += new FileSystemEventHandler(FileWatcher_Deleted);
                _watcher.Renamed += new RenamedEventHandler(FileWatcher_Renamed);
            }

            catch ( Exception ex )
            {
                Console.WriteLine("Error:" + ex.Message);
            }
        }


        public void Start()
        {
            // 开始监听
            this._watcher.EnableRaisingEvents = true;
            Console.WriteLine(string.Format("==========【{0}】==========", "文件监控已经启动..."));

        }

        public void Stop()
        {

            this._watcher.EnableRaisingEvents = false;
            this._watcher.Dispose();
            this._watcher = null;
            Console.WriteLine(string.Format("==========【{0}】==========", "文件监控已经关闭"));
        }
        /// <summary>
        /// 添加插件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void FileWatcher_Created(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine(string.Format("==========【{0}】==========", "添加" + e.Name));
            var dll = new FileInfo(e.FullPath);
            var fileData = File.ReadAllBytes(dll.FullName);
            Assembly asm = Assembly.Load(fileData);
            var manifestModuleName = asm.ManifestModule.ScopeName;
            var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf("."));
            Type type = asm.GetType("Plugin_Test" + "." + classLibrayName);
            // 这里默认不替换之前的插件内容
            if ( _iPlugin.ContainsKey(classLibrayName) )
            {
                Console.WriteLine("已经加载该插件");
                return;
            }
            if ( !typeof(IPlugin).IsAssignableFrom(type) )
            {
                Console.WriteLine($"{asm.ManifestModule.Name}未继承约定接口");
                return;
            }
            //dll实例化
            var instance = Activator.CreateInstance(type) as IPlugin;
            var protocolInfo = instance.GetPluginInformation();
            protocolInfo.LastTime = dll.LastWriteTime;
            Console.WriteLine($"插件名称:{protocolInfo.Name}");
            Console.WriteLine($"插件版本:{protocolInfo.Version}");
            Console.WriteLine($"插件作者:{protocolInfo.Author}");
            Console.WriteLine($"插件时间:{protocolInfo.LastTime}");
            _iPlugin.Add(classLibrayName, instance);
            _iPluginInfos.Add(classLibrayName, protocolInfo);
            //释放插件资源
            instance.Dispose();
            instance = null;
            Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count);
        }
        /// <summary>
        /// 修改插件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void FileWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            string pluginName = e.Name.Split(".")[0];
            var dll = new FileInfo(e.FullPath);
            // 替换插件
            if ( _iPluginInfos.ContainsKey(pluginName) )
            {
                // 修改时间不一致,说明是新的插件
                if ( _iPluginInfos[pluginName].LastTime != dll.LastWriteTime)
                {
                    Console.WriteLine(string.Format("==========【{0}】==========", "修改" + e.Name));
                    // 更新
                    var fileData = File.ReadAllBytes(e.FullPath);
                    Assembly asm = Assembly.Load(fileData);
                    var manifestModuleName = asm.ManifestModule.ScopeName;
                    var classLibrayName = manifestModuleName.Remove(manifestModuleName.LastIndexOf("."), manifestModuleName.Length - manifestModuleName.LastIndexOf("."));
                    Type type = asm.GetType("Plugin_Test" + "." + classLibrayName);
                    if ( !typeof(IPlugin).IsAssignableFrom(type) )
                    {
                        Console.WriteLine($"{asm.ManifestModule.Name}未继承约定接口");
                        return;
                    }
                    var instance = Activator.CreateInstance(type) as IPlugin;
                    var protocolInfo = instance.GetPluginInformation();
                    protocolInfo.LastTime = dll.LastWriteTime;
                    Console.WriteLine($"插件名称:{protocolInfo.Name}");
                    Console.WriteLine($"插件版本:{protocolInfo.Version}");
                    Console.WriteLine($"插件作者:{protocolInfo.Author}");
                    Console.WriteLine($"插件时间:{protocolInfo.LastTime}");
                    _iPlugin[classLibrayName] = instance;
                    _iPluginInfos[classLibrayName] = protocolInfo;
                    instance.Dispose();
                    instance = null;
                    // 避免多次触发
                    this._watcher.EnableRaisingEvents = false;
                    this._watcher.EnableRaisingEvents = true;
                    Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count);
                }
            }
        }
        /// <summary>
        /// 删除插件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void FileWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine(string.Format("==========【{0}】==========", "删除" + e.Name));
            string pluginName = e.Name.Split(".")[0];
            if ( _iPlugin.ContainsKey(pluginName) )
            {
                _iPlugin.Remove(pluginName);
                _iPluginInfos.Remove(pluginName);
                Console.WriteLine($"插件{e.Name}被移除");
            }
            Console.WriteLine(string.Format("==========【{0}】==========", "共加载插件{0}个"), _iPlugin.Count);
        }
        /// <summary>
        /// 重命名
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void FileWatcher_Renamed(object sender, RenamedEventArgs e)
        {
            //TODO:暂时不做处理
            Console.WriteLine("重命名" + e.OldName + "->" + e.Name);
            //Console.WriteLine("重命名: OldPath:{0} NewPath:{1} OldFileName{2} NewFileName:{3}", e.OldFullPath, e.FullPath, e.OldName, e.Name);
        }

    2.2 EnableRaisingEvents 控制是否启用,这个类的修改方法很容易被多次调用,因此用以下代码避免多次触发:

// 避免多次触发
this._watcher.EnableRaisingEvents = false;
this._watcher.EnableRaisingEvents = true;

    2.3 这样,每当这个指定文件夹下的dll发生变化时,就会进行相应的操作,重新加载到内存中,其测试结果如下:

  

     2.4 当然,这只是一个简单的小demo,还会有很多问题,希望以后遇到了再改进。

参考:

  https://blog.csdn.net/daoer_sofu/article/details/70473691

  https://www.cnblogs.com/winformasp/articles/10893922.html

标签:插件,Console,string,c#,热插拔,protocolInfo,WriteLine,var
来源: https://www.cnblogs.com/chenzibai/p/15855331.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有