插件 框架 搭建(记录一下)

30 篇文章 0 订阅
15 篇文章 0 订阅

目录

背景

设计需要

框架设计

1.设计插件接口

2.插件辅助类设计

3.设计插件

4.插件系统设计

开发中遇到的问题:

后话



背景

大家设计软件的时候,基本都是封装成库的形式来使用, 虽说这种方式大大提高了灵活性,但是远远达不到所加即所得.插件虽然也是库的形式,但是由于其设计理念.插件的灵活性远远大于单纯的库.当然,其中一些编写难度也随之提升.

之前完成的项目,由于考虑到后期项目的灵活可配置.所以需要进行项目插件化(不亚于重写)

设计需要

里面核心就是:设计模式中的策略模式+ 迪米特依赖倒置法则. 由于这里使用了wpf这个框架(以后把Qt那版找机会发出来),所以 .net本身的优势. 反射, 结合这些知识,我们就可以着手设计了

功能

批量加载, 加载特定插件. 解析文本, 刷新等功能. 右键选择应用以及卸载插件

框架设计

1.设计插件接口

1.1 这里要设计库的类型: 

public enum IInterFaceTypeEnum : byte
    {

        NoEnum = 0x00,       //无类型
        UIEnum = 0x01,       //带界面库的类型.
        StandardEnum = 0x02  //普通库.(标准库,只存在功能)

    }

   1.2.1,创建插件接口

 ///创建接口.包含一些必要的信息.
    public interface IEearthControl
    {

        //插件名称.插件描述.处理参数. 处理方法.

        /// <summary>
        /// 插件名称
        /// </summary>
        string PluginName { get; }

        /// <summary>
        /// 插件描述
        /// </summary>
        string PluginDesc { get; }

        /// <summary>
        /// 处理参数
        /// </summary>
        string PluginPare { get; }

        ///命名空间.类型
        string PluginNameSpaceAndClassName { get; }

        /// <summary>
        /// 处理方法
        /// </summary>
        string Execute(string str);


        IInterFaceTypeEnum UIType { get; }

    }

 1.2.2 封装接口到具体一个实现类

 

 public class PluginModel
    {
        public PluginModel(IEearthControl plugin)
        {
            Plugin = plugin;
        }

        /// <summary>
        /// 插件实例
        /// </summary>
        public IEearthControl Plugin { get; }

        /// <summary>
        /// 插件名称
        /// </summary>
        public string PluginName => Plugin?.PluginName;

        /// <summary>
        /// 插件描述
        /// </summary>
        public string PluginDesc => Plugin?.PluginDesc;

        /// <summary>
        /// 处理参数
        /// </summary>
        public string PluginPare => Plugin?.PluginPare;

        /// <summary>
        /// 插件命名空间+类型
        /// </summary>
        public string PluginNamespaceAndClassName => Plugin?.PluginNameSpaceAndClassName;

        public string Execute(string str)
        {
           return Plugin?.Execute(str);
        }

        /// <summary>
        /// 
        /// </summary>
        public IInterFaceTypeEnum UIType => Plugin.UIType;

    }

到此. 插件接口设计完成. 这个单独在一个pluginBase的库中.

2.插件辅助类设计

  这里主要是对继承上面接口的插件进行 识别,加载,卸载,更新,消息触发等操作.

  核心代码:

  描述: 使用反射,以及 做一个全局键值对集合对加载的DLL,进行控制,避免重复加载

   /// <summary>
    /// 包含加载插件需要的程序集信息,以及描述信息
    /// </summary>
    public class IEarthModel:IDisposable
    {
        private PluginModel _pluginModel;
        public PluginModel pluginModel 
        {
            get=> _pluginModel;
            set=> _pluginModel=value;
        }

        private Type _type;
        public Type Modeltype 
        { 
            get=> _type;
            set=> _type=value; 
        }

        public IEarthModel(PluginModel _pluginModel,Type _type)
        {
           this._pluginModel = _pluginModel;
            this._type = _type;
        }


        /// <summary>
        /// 动态创建对象.
        /// </summary>
        /// <returns></returns>
        public dynamic CreateObj()
        {
            return Activator.CreateInstance(_type);
        }

        public IEarthModel() { }

        public override string ToString()
        {
            return $"{pluginModel.PluginName} {pluginModel.PluginDesc} {pluginModel.UIType}";
        }

        public void Dispose()
        {
            _pluginModel = null;
            _type = null;
            GC.Collect();
        }
    }
/// <summary>
    /// 插件辅助工具类
    /// 包含加载插件,删除插件.批量加载插件内等操作.
    /// </summary>
    public class PluginHelper
    {



   /// <summary>
        /// 加载特定插件.
        /// 
        /// </summary>
        /// <param name="PluginFullName">DLL,全路径</param>
        /// <returns></returns>
        public bool LoadPlugin(string PluginFullName)
        {
            try
            {
                byte[] buffer = File.ReadAllBytes(PluginFullName);
                Assembly assembly = Assembly.Load(buffer);
                //创建对象.这里需要命名空间+类名字.来创建Type对象.
                Type[] types = assembly.GetTypes();
                //查找到目标
                foreach (var item in types)
                {
                    if (item.GetInterface(typeof(IEearthControl).Name) != null)
                    {
                        IEearthControl Ic = Activator.CreateInstance(item) as IEearthControl;
                        IEarthModel IModele = new IEarthModel();
                        IModele.pluginModel = new PluginModel(Ic);
                        IModele.Modeltype = assembly.GetType(Ic.PluginNameSpaceAndClassName);
                        if (AddDictionary(ref IModele) == true)
                        {
                            IplugList.Add(IModele);
                            CallBackShowErrorMethod($"Success:插件: {IModele.pluginModel.PluginName} 加载成功");
                        }
                        else
                        {
                            CallBackShowErrorMethod($"warning: 插件: {IModele.pluginModel.PluginName} 已经存在,请务重复添加!");
                        }
                        Update();
                    }
                }

            }
            catch (Exception ex)
            {
                CallBackShowErrorMethod($"Error: PluginHelper.LoadPlugin() {ex.Message}");
                return false;
            }
            return true;
        }

        public string ExeCute(string str)
        {
            foreach (var item in IplugList)
            {
                if (item.pluginModel.UIType == IInterFaceTypeEnum.UIEnum) continue;
                str =  item.pluginModel.Execute(str);
            }
            return str;
        }

        ///卸载插件
        public void RemovePlugin(IEarthModel earthModel)
        {
            RemoveDictionary(ref earthModel);
            IplugList.Remove(earthModel);
            Update();
            CallBackShowErrorMethod($"Success: 插件: {earthModel.pluginModel.PluginName} 卸载成功");
        }

}

 到此, PluginHelper库设计完成

3.设计插件

 

关键部分: 继承接口 IEearthControl, 实现接口规范

 /// <summary>
    /// UserControl1.xaml 的交互逻辑
    /// </summary>
    public partial class UserControl1 : UserControl,IEearthControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        #region 消息设置.
        public string PluginName => @"UI_TextBox";

        public string PluginDesc => @"设置信息展示";

        public string PluginPare => @"测试UI功能,并选择时间";

        public string PluginNameSpaceAndClassName => @"UITextBox.UserControl1";

        public IInterFaceTypeEnum UIType => IInterFaceTypeEnum.UIEnum;

        public string Execute(string str)
        {
            return "Execute Null";
        } 
        #endregion
    }

 编译,在指定的Plugins文件夹,搜索dll库

其它三个库,都是测试的, 一个只执行文本解析功能,另外两个,执行带UI控件库的功能.

4.插件系统设计

 4.1界面部分

 <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="60"></RowDefinition>
            <RowDefinition></RowDefinition>
            <RowDefinition Height="50"></RowDefinition>
        </Grid.RowDefinitions>
        <Border Grid.Row="0">
            <DockPanel LastChildFill="True">
                <Label DockPanel.Dock="Left" VerticalAlignment="Center">插件文件夹:</Label>
                <Button x:Name="BtnSelectFolder" Click="BtnSelectFolder_Click" DockPanel.Dock="Right" Margin="5,0" VerticalAlignment="Center" Width="80">选择文件夹</Button>
                <TextBox VerticalAlignment="Center" x:Name="txtPluginFolder" ></TextBox>
            </DockPanel>
        </Border>
        <Border Grid.Row="1">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="3*"></ColumnDefinition>
                    <ColumnDefinition Width="2*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Border>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition></RowDefinition>
                            <RowDefinition Height="80"></RowDefinition>
                        </Grid.RowDefinitions>
                        <DataGrid x:Name="dgVPluginInfo" 
                              AutoGenerateColumns="False"  VerticalAlignment="Top"
                                  CanUserSortColumns="true"    Margin="5" IsReadOnly="True"
                                  CanUserResizeColumns="true" CanUserResizeRows="False"  SelectionMode="Single"
                                  CanUserReorderColumns="False" AlternationCount="2"  RowHeaderWidth="0" CanUserAddRows="False" >
                            <DataGrid.Columns>
                                <DataGridTextColumn Width="*"  Header="插件名字" Binding="{Binding pluginModel.PluginName}"/>
                                <DataGridTextColumn Width="*" Header="插件类型" Binding="{Binding pluginModel.UIType}"/>
                                <DataGridTextColumn Width="*"  Header="插件参数" Binding="{Binding pluginModel.PluginPare}">
                                </DataGridTextColumn>
                                <DataGridTextColumn Width="*" Header="插件描述" Binding="{Binding pluginModel.PluginDesc}"/>
                            </DataGrid.Columns>
                            <DataGrid.ContextMenu>
                                <ContextMenu>
                                    <Button  Click="AddPlugin" HorizontalAlignment="Stretch" BorderThickness="0">加载UI插件</Button>
                                    <Button  Click="RemovePlugin" BorderThickness="0">卸      载</Button>
                                </ContextMenu>
                            </DataGrid.ContextMenu>
                        </DataGrid>
                        <Border Grid.Row="1">
                            <ListBox x:Name="InforList" VirtualizingPanel.CacheLength="20" ScrollViewer.CanContentScroll="True" VirtualizingPanel.IsVirtualizing="True"></ListBox>
                        </Border>
                    </Grid>
                </Border>
                <Border Grid.Column="1">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition></RowDefinition>
                            <RowDefinition></RowDefinition>
                        </Grid.RowDefinitions>
                        <Grid Grid.Row="0">
                            <GroupBox Header="StandardEnum (标准文本解析区域)"  Margin="5">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition></ColumnDefinition>
                                        <ColumnDefinition></ColumnDefinition>
                                    </Grid.ColumnDefinitions>
                                    <TextBox Grid.Column="0" x:Name="txtTo" TextWrapping="Wrap"></TextBox>
                                    <TextBox x:Name="txtFrom" Grid.Column="1" TextWrapping="Wrap" AcceptsReturn="True"></TextBox>
                                </Grid>
                            </GroupBox>
                        </Grid>
                        <GroupBox Grid.Row="1" Header="UIEnum (UI插件加载区域)" Margin="5">
                            <Border  Background="AliceBlue" x:Name="PanelName"></Border>
                        </GroupBox>
                    </Grid>
                </Border>
            </Grid>
        </Border>


        <Border Grid.Row="2">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="3*"></ColumnDefinition>
                    <ColumnDefinition Width="2*"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Border>
                    <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                        <Label>插件数量:</Label>
                        <Label x:Name="lblPluginCount">0</Label>
                    </StackPanel>
                </Border>
                <Border Grid.Column="1">
                    <Border.Resources>
                        <Style TargetType="{x:Type Button}">
                            <Setter Property="Width" Value="80"></Setter>
                            <Setter Property="VerticalAlignment" Value="Center"></Setter>
                            <Setter Property="Margin" Value="5,0,5,0"></Setter>
                            <Setter Property="FontSize" Value="11"></Setter>
                        </Style>
                    </Border.Resources>
                    <StackPanel  Orientation="Horizontal" HorizontalAlignment="Center">
                        <Button x:Name="BtnLoadPlugin" Click="BtnLoadPlugins_Click">批量加载插件</Button>
                        <Button x:Name="BtnAnalysize" Click="BtnAnalysize_Click" >解析文本</Button>
                        <Button  Click="BtnLoadSiglePlugin_Click">加载单独插件</Button>
                        <Button Click="updateClicked">刷新</Button>
                    </StackPanel>
                </Border>
            </Grid>
        </Border>
    </Grid>

4.2 后台关键部分

  private PluginHelper.PluginHelper gPluginHelper { get; set; } 
/// <summary>
        /// 批量加载插件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>

        private void BtnLoadPlugins_Click(object sender, RoutedEventArgs e)
        {
            if (gPluginHelper == null)
            {
                gPluginHelper = new PluginHelper.PluginHelper(txtPluginFolder.Text);
                gPluginHelper.setCallBackMethod(showLogInfo);
            }
            if (gPluginHelper!=null)
            {
                if(!gPluginHelper.LoadPlugin())
                {
                    gPluginHelper = null;
                    MessageBox.Show("插件加载失败");
                    return;
                }
                else
                {
                    if (gPluginHelper.getIPLuginList.Count != 0)
                    {
                        updateListSource();
                    }
                    else
                    {
                        dgVPluginInfo.ItemsSource = null;
                    }
                }
            }
        }

 private void RemoveChild(IEarthModel earthModel)
        {
            if (earthModel != null && gPluginHelper!=null)
            {
                gPluginHelper.RemovePlugin(earthModel);
            }
        }

到此,我们的系统完成了对 插件的自动加载, 识别,热拔插 热加载等等功能,展示一下

 

开发中遇到的问题:

注意: 如果对同一个插件多次通过反射加载, 那么势必保证在插件队列中保存最新的那个对象, 因为Assembly这个加载的dll,程序集域中只保留最后一次加载的二进制数据信息. 所以,如果再次调用的话,必须将原来队列中相应的对象进行更新! 否则Activator.CreateInstance(_type)调用的时候,其会导致xmal中的资源失效,进而导致整个插件应用异常!

 public void updateModel(ref IEarthModel earthModel)
        {
            for (int i=0;i<IplugList.Count;i++)
            {
                IEarthModel temp = IplugList[i];
                if(temp.ToString()== earthModel.ToString())
                {
                    IplugList[i] = earthModel;
                    break;
                }
            }
            //更新Dictionary
            if(PluginDictinary.ContainsKey(earthModel.ToString()))
            {
                PluginDictinary[earthModel.ToString()] = earthModel;
            }

        }

后话

以后自己封装的所有功能,通过这个,就可以随时随地的使用了,而不用到处找,不过后面我打算设计一个config配置文件,通过配置文件,有选择的加载.让这个小型系统更完善一些

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值