前言:

 近期项目中需要实现“热插拔”式的插件程序,例如:定义一个插件接口;由不同开发人员实现具体的插件功能类库;并最终在应用中调用具体插件功能。

 此时需要考虑:插件执行的安全性(隔离运行)和插件可卸载升级。说到隔离运行和可卸载首先想到的是AppDomain。

 那么AppDomain是什么呢?

一、AppDomain介绍

 AppDomain是.Net平台里一个很重要的特性,在.Net以前,每个程序是"封装"在不同的进程中的,这样导致的结果就造就占用资源大,可复用性低等缺点.而AppDomain在同一个进程内划分出多个"域",一个进程可以运行多个应用,提高了资源的复用性,数据通信等. 详见

 CLR在启动的时候会创建系统域(System Domain),共享域(Shared Domain)和默认域(Default Domain),系统域与共享域对于用户是不可见的,默认域也可以说是当前域,它承载了当前应用程序的各类信息(堆栈),所以,我们的一切操作都是在这个默认域上进行."插件式"开发很大程度上就是依靠AppDomain来进行.

 应用程序域具有以下特点:

  • 必须先将程序集加载到应用程序域中,然后才能执行该程序集。

  • 一个应用程序域中的错误不会影响在另一个应用程序域中运行的其他代码。

  • 能够在不停止整个进程的情况下停止单个应用程序并卸载代码。不能卸载单独的程序集或类型,只能卸载整个应用程序域。

二、基于AppDomain实现“热拔式插件”

 通过AppDomain来实现程序集的卸载,这个思路是非常清晰的。由于在程序设计中,非特殊的需要,我们都是运行在同一个应用程序域中。

 由于程序集的卸载存在上述的缺陷,我们必须要关闭应用程序域,方可卸载已经装载的程序集。然而主程序域是不能关闭的,因此唯一的办法就是在主程序域中建立一个子程序域,通过它来专门实现程序集的装载。一旦要卸载这些程序集,就只需要卸载该子程序域就可以了,它并不影响主程序域的执行。 

 实现方式如下图:

  

1、AssemblyDynamicLoader类提供创建子程序域和卸载程序域的方法;
2、RemoteLoader类提供装载程序集、执行接口方法;
3、AssemblyDynamicLoader类获得RemoteLoader类的代理对象,并调用RemoteLoader类的方法;
4、RemoteLoader类的方法在子程序域中完成;

 那么AssemblyDynamicLoader 和 RemoteLoader 如何实现呢?

 1、首先定义RemoteLoader用于加载插件程序集,并提供插件接口执行方法  

public class RemoteLoader : MarshalByRefObject
{
private Assembly _assembly; public void LoadAssembly(string assemblyFile)
{
_assembly = Assembly.LoadFrom(assemblyFile);
}
public T GetInstance<T>(string typeName) where T : class
{
if (_assembly == null) return null;
var type = _assembly.GetType(typeName);
if (type == null) return null;
return Activator.CreateInstance(type) as T;
}
public object ExecuteMothod(string typeName, string args)
{
if (_assembly == null) return null;
var type = _assembly.GetType(typeName);
var obj = Activator.CreateInstance(type);
if (obj is IPlugin)
{
return (obj as IPlugin).Exec(args);
}
return null;
}
}

  由于每个AppDomain都有自己的堆栈,内存块,也就是说它们之间的数据并非共享了.若想共享数据,则涉及到应用程序域之间的通信.C#提供了MarshalByRefObject类进行跨域通信,则必须提供自己的跨域访问器.

 2、AssemblyDynamicLoader 主要用于管理应用程序域创建和卸载;并创建RemoteLoader对象

using System;
using System.IO;
using System.Reflection;
namespace PluginRunner
{
public class AssemblyDynamicLoader
{
private AppDomain appDomain;
private RemoteLoader remoteLoader;
public AssemblyDynamicLoader(string pluginName)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "app_" + pluginName;
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
setup.PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
setup.CachePath = setup.ApplicationBase;
setup.ShadowCopyFiles = "true";
setup.ShadowCopyDirectories = setup.ApplicationBase;
AppDomain.CurrentDomain.SetShadowCopyFiles();
this.appDomain = AppDomain.CreateDomain("app_" + pluginName, null, setup); String name = Assembly.GetExecutingAssembly().GetName().FullName;
this.remoteLoader = (RemoteLoader)this.appDomain.CreateInstanceAndUnwrap(name, typeof(RemoteLoader).FullName);
}
/// <summary>
/// 加载程序集
/// </summary>
/// <param name="assemblyFile"></param>
public void LoadAssembly(string assemblyFile)
{
remoteLoader.LoadAssembly(assemblyFile);
}
/// <summary>
/// 创建对象实例
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="typeName"></param>
/// <returns></returns>
public T GetInstance<T>(string typeName) where T : class
{
if (remoteLoader == null) return null;
return remoteLoader.GetInstance<T>(typeName);
}
/// <summary>
/// 执行类型方法
/// </summary>
/// <param name="typeName"></param>
/// <param name="methodName"></param>
/// <returns></returns>
public object ExecuteMothod(string typeName, string methodName)
{
return remoteLoader.ExecuteMothod(typeName, methodName);
}
/// <summary>
/// 卸载应用程序域
/// </summary>
public void Unload()
{
try
{
if (appDomain == null) return;
AppDomain.Unload(this.appDomain);
this.appDomain = null;
this.remoteLoader = null;
}
catch (CannotUnloadAppDomainException ex)
{
throw ex;
}
}
public Assembly[] GetAssemblies()
{
return this.appDomain.GetAssemblies();
}
}
}

 3、插件接口和实现:

  插件接口:

public interface IPlugin
{ /// <summary>
/// 执行插件方法
/// </summary>
/// <param name="pars">参数json</param>
/// <returns>执行结果json串</returns>
object Exec(string pars); /// <summary>
/// 插件初始化
/// </summary>
/// <returns></returns>
bool Init(); }

  测试插件实现:

public class PrintPlugin : IPlugin
{
public object Exec(string pars)
{
//v1.0
//return $"打印插件执行-{pars} 完成";
//v1.1
return $"打印插件执行-{pars} 完成-更新版本v1.1";
} public bool Init()
{
return true;
}
}

 4、插件执行:

string pluginName = txtPluginName.Text;
if (!string.IsNullOrEmpty(pluginName) && PluginsList.ContainsKey(pluginName))
{
var loader = PluginsList[pluginName];
var strResult = loader.ExecuteMothod("PrintPlugin.PrintPlugin", "Exec")?.ToString();
MessageBox.Show(strResult);
}
else
{
MessageBox.Show("插件未指定或未加载");
}

 5、测试界面实现:

  创建个测试窗体如下:

三、运行效果

 插件测试基本完成:那么看下运行效果:可以看出当前主程序域中未加载PrintPlugin.dll,而是在子程序集中加载

  

 当我们更新PrintPlugin.dll逻辑,并更新测试程序加载位置中dll,不会出现不允许覆盖提示;然后先卸载dll在再次加载刚刚dll(模拟插件升级)

  

 到此已实现插件化的基本实现

四、其他

 当然隔离运行和“插件化”都还有其他实现方式,等着解锁。但是只要搞清楚本质、实现原理、底层逻辑这些都相对简单。所以对越基础的内容越要理解清楚。

参考:

 官网介绍:https://docs.microsoft.com/zh-cn/dotnet/framework/app-domains/application-domains

 示例源码:https://github.com/cwsheng/PluginAppDemo

AppDomain实现【插件式】开发的更多相关文章

  1. 基于AppDomain的"插件式"开发

    很多时候,我们都想使用(开发)USB式(热插拔)的应用,例如,开发一个WinForm应用,并且这个WinForm应用能允许开发人员定制扩展插件,又例如,我们可能维护着一个WinService管理系统, ...

  2. C#学习笔记-----基于AppDomain的"插件式"开发

    很多时候,我们都想使用(开发)USB式(热插拔)的应用,例如,开发一个WinForm应用,并且这个WinForm应用能允许开发人员定制扩展插件,又例如,我们可能维护着一个WinService管理系统, ...

  3. MEF 插件式开发 - 小试牛刀

    原文:MEF 插件式开发 - 小试牛刀 目录 MEF 简介 实践出真知 面向接口编程 控制反转(IOC) 构建入门级 MEF 相关参考 MEF 简介 Managed Extensibility Fra ...

  4. MEF 插件式开发 - WPF 初体验

    原文:MEF 插件式开发 - WPF 初体验 目录 MEF 在 WPF 中的简单应用 加载插件 获取元数据 依赖注入 总结 MEF 在 WPF 中的简单应用 MEF 的开发模式主要适用于插件化的业务场 ...

  5. 从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用ApplicationPart动态加载控制器和视图

    标题:从零开始实现ASP.NET Core MVC的插件式开发(一) - 使用Application Part动态加载控制器和视图 作者:Lamond Lu 地址:http://www.cnblogs ...

  6. 从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件

    标题:从零开始实现ASP.NET Core MVC的插件式开发(三) - 如何在运行时启用组件 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/112 ...

  7. 从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装

    标题:从零开始实现ASP.NET Core MVC的插件式开发(四) - 插件安装 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/11260750. ...

  8. 从零开始实现ASP.NET Core MVC的插件式开发(五) - 插件的删除和升级

    标题:从零开始实现ASP.NET Core MVC的插件式开发(五) - 使用AssemblyLoadContext实现插件的升级和删除 作者:Lamond Lu 地址:https://www.cnb ...

  9. 从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用

    标题:从零开始实现ASP.NET Core MVC的插件式开发(六) - 如何加载插件引用. 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1171 ...

  10. 从零开始实现ASP.NET Core MVC的插件式开发(九) - 升级.NET 5及启用预编译视图

    标题:从零开始实现ASP.NET Core MVC的插件式开发(九) - 如何启用预编译视图 作者:Lamond Lu 地址:https://www.cnblogs.com/lwqlun/p/1399 ...

随机推荐

  1. css animation & animationend event & onanimationend

    css animation & animationend event & onanimationend https://developer.mozilla.org/en-US/docs ...

  2. how to write a node cli tool

    how to write a node cli tool https://www.google.com/search?q=how+to+node+cli+tool&oq=how+to+node ...

  3. Web 安全 & 反爬虫原理

    Web 安全 & 反爬虫原理 数据加密/解密 HTTPS ip 封锁 请求限制 爬虫识别,canvas 指纹 refs https://segmentfault.com/a/119000001 ...

  4. flutter 插件调用callback函数

    dart plugin class TestLib { static MethodChannel _channel = const MethodChannel('test_lib') ..setMet ...

  5. vue components registration & vue error & Unknown custom element

    vue components registration & vue error & Unknown custom element vue.esm.js:629 [Vue warn]: ...

  6. dart 匹配基本map

    var map_start = RegExp(r'^\s*\{\s*'); var map_end = RegExp(r'^\}\s*(,)?\s*'); var hasComma = true; M ...

  7. SSL/TLS协议详解(下)——TLS握手协议

    本文转载自SSL/TLS协议详解(下)--TLS握手协议 导语 在博客系列的第2部分中,对证书颁发机构进行了深入的讨论.在这篇文章中,将会探索整个SSL/TLS握手过程,在此之前,先简述下最后这块内容 ...

  8. ElementUI使用总结

    首先声明,我这总结的官网都有,只是将自己使用时遇到的问题,重新记录一下,官网地址:https://element.eleme.cn/ 1.表格内指定行数给定不同样式(类似于隔行变色,也能叫指定行数不同 ...

  9. Lambad表达式--Java8新特性

    1.概述 Lambda是一个匿名函数,是java8的一个新特性.可以对接口进行非常简洁的实现.但它要求接口中只能有一个抽象方法,原因是lambda只能实现一个方法.另外,需要在接口上添加注解@Func ...

  10. Java 搭建 RabbitMq 消息中间件

    前言 当系统中出现"生产"和"消费"的速度或稳定性等因素不一致的时候,就需要消息队列. 名词 exchange: 交换机 routingkey: 路由key q ...