目录
背景
大家设计软件的时候,基本都是封装成库的形式来使用, 虽说这种方式大大提高了灵活性,但是远远达不到所加即所得.插件虽然也是库的形式,但是由于其设计理念.插件的灵活性远远大于单纯的库.当然,其中一些编写难度也随之提升.
之前完成的项目,由于考虑到后期项目的灵活可配置.所以需要进行项目插件化(不亚于重写)
设计需要
里面核心就是:设计模式中的策略模式+ 迪米特依赖倒置法则. 由于这里使用了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配置文件,通过配置文件,有选择的加载.让这个小型系统更完善一些