当前位置: 首页 > news >正文

.NET Core 中插件式开发实现

在 .NET Framework 中,通过AppDomain实现动态加载和卸载程序集的效果;但是.NET Core 仅支持单个默认应用域,那么在.NET Core中如何实现【插件式】开发呢?

一、.NET Core 中 AssemblyLoadContext的使用

1、AssemblyLoadContext简介:

每个 .NET Core 应用程序均隐式使用 AssemblyLoadContext。它是运行时的提供程序,用于定位和加载依赖项。只要加载了依赖项,就会调用 AssemblyLoadContext 实例来定位该依赖项。

  • 它提供定位、加载和缓存托管程序集和其他依赖项的服务。

  • 为了支持动态代码加载和卸载,它创建了一个独立上下文,用于在其自己的 AssemblyLoadContext 实例中加载代码及其依赖项。

2、AssemblyLoadContext和AppDomain卸载差异:

使用 AssemblyLoadContext 和使用 AppDomain 进行卸载之间存在一个值得注意的差异。对于 Appdomain,卸载为强制执行。

卸载时,会中止目标 AppDomain 中运行的所有线程,会销毁目标 AppDomain 中创建的托管 COM 对象,等等。对于 AssemblyLoadContext,卸载是“协作式的”。

调用 AssemblyLoadContext.Unload 方法只是为了启动卸载。以下目标达成后,卸载完成:

1、没有线程将程序集中的方法加载到其调用堆栈上的 AssemblyLoadContext 中。

2、程序集中的任何类型都不会加载到 AssemblyLoadContext,这些类型的实例本身由以下引用:

    • AssemblyLoadContext 外部的引用,弱引用(WeakReference 或 WeakReference)除外。

    • AssemblyLoadContext 内部和外部的强垃圾回收器 (GC) 句柄(GCHandleType.Normal 或 GCHandleType.Pinned)。

二、.NET Core 插件式方式实现

1、创建可卸载的上下文PluginAssemblyLoadContext

class PluginAssemblyLoadContext : AssemblyLoadContext
{private AssemblyDependencyResolver _resolver;/// <summary>/// 构造函数/// isCollectible: true 重点,允许Unload/// </summary>/// <param name=pluginPath></param>public PluginAssemblyLoadContext(string pluginPath) : base(isCollectible: true){_resolver = new AssemblyDependencyResolver(pluginPath);}protected override Assembly Load(AssemblyName assemblyName){string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);if (assemblyPath != null){return LoadFromAssemblyPath(assemblyPath);}return null;}protected override IntPtr LoadUnmanagedDll(string unmanagedDllName){string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);if (libraryPath != null){return LoadUnmanagedDllFromPath(libraryPath);}return IntPtr.Zero;}
}

2、创建插件接口及实现

整体项目结构为:

图片

a)添加项目PluginInterface,插件接口:

public interface IPlugin
{string Name { get; }string Description { get; }string Execute(object inPars);
}

b)添加HelloPlugin项目,实现不引用外部dll插件

public class HelloPlugin : PluginInterface.IPlugin
{public string Name => "HelloPlugin";public string Description { get => "Displays hello message."; }public string Execute(object inPars){return ("Hello !!!" + inPars?.ToString());    } }

c)添加JsonPlugin项目,实现引用三方dll插件

public class JsonPlugin : PluginInterface.IPlugin
{public string Name => "JsonPlugin";public string Description => "Outputs JSON value.";private struct Info{public string JsonVersion;public string JsonLocation;public string Machine;public DateTime Date;}public string Execute(object inPars){Assembly jsonAssembly = typeof(JsonConvert).Assembly;Info info = new Info(){JsonVersion = jsonAssembly.FullName,JsonLocation = jsonAssembly.Location,Machine = Environment.MachineName,Date = DateTime.Now};return JsonConvert.SerializeObject(info, Formatting.Indented);}
}

d)添加PluginsApp项目,实现调用插件方法:

修改窗体界面布局:

图片

添加执行方法

/// <summary>
/// 将此方法标记为noinline很重要,否则JIT可能会决定将其内联到Main方法中。
/// 这可能会阻止成功卸载插件,因为某些实例的生存期可能会延长到预期卸载插件的时间点之外。
/// </summary>
/// <param name="assemblyPath"></param>
/// <param name="inPars"></param>
/// <param name="alcWeakRef"></param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.NoInlining)]
static string ExecuteAndUnload(string assemblyPath, object inPars, out WeakReference alcWeakRef)
{string resultString = string.Empty;// 创建 PluginLoadContext对象var alc = new PluginAssemblyLoadContext(assemblyPath);//创建一个对AssemblyLoadContext的弱引用,允许我们检测卸载何时完成alcWeakRef = new WeakReference(alc);// 加载程序到上下文// 注意:路径必须为绝对路径.Assembly assembly = alc.LoadFromAssemblyPath(assemblyPath);//创建插件对象并调用foreach (Type type in assembly.GetTypes()){if (typeof(IPlugin).IsAssignableFrom(type)){IPlugin result = Activator.CreateInstance(type) as IPlugin;if (result != null){resultString = result.Execute(inPars);break;}}}//卸载程序集上下文alc.Unload();return resultString;
}

三、效果验证

1、非引用外部dll的插件执行:执行后对应dll成功卸载,程序集数量未增加。

图片


2、引用外部包的插件:执行后对应dll未卸载,程序集数量增加。

图片


通过监视查看对象状态:该上下文在卸载中。暂未找到原因卸载失败(疑问?)

图片

四、总结

虽然微软文档说.NET Core中使用AssemblyLoadContext来实现程序集的加载及卸载实现,但通过验证在加载引用外部dll后,加载后不能正常卸载。或者使用方式还不正确。

源码地址:https://github.com/cwsheng/PluginsApp

相关文章:

  • 使用vscode实现远程开发,并通过内网穿透在公网环境下远程连接
  • module ‘torch‘ has no attribute ‘_six‘问题解决
  • 爬虫试用 | 京东商品详情搜索采集助手 – 一键批量采集下载商品详情
  • AnyLink 安装教程(docker)
  • 字符型液晶显示器LCD 1602的显示控制(Keil+Proteus)
  • 自定义 Spring Boot Starter 组件
  • 劝学:Android 14 Framework 引入了哪些“新”技术栈
  • 【C++数据结构】异常简介与异常类的构建
  • 【云原生】使用nginx反向代理后台多服务器
  • 安全防御——三、网络安全理论知识
  • npm ERR! code ERESOLVE,npm ERR! ERESOLVE unable to resolve dependency tree
  • word统计全部字符数。
  • IDEA使用Git进行代码管理教程
  • 利用shp文件构建mask【MATLAB和ARCGIS】两种方法
  • 【达梦数据库】mysql与达梦整数类型对比关系
  • JavaScript-如何实现克隆(clone)函数
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • 03Go 类型总结
  • Docker 笔记(2):Dockerfile
  • gcc介绍及安装
  • httpie使用详解
  • HTTP--网络协议分层,http历史(二)
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • log4j2输出到kafka
  • PAT A1017 优先队列
  • redis学习笔记(三):列表、集合、有序集合
  • 基于遗传算法的优化问题求解
  • 看域名解析域名安全对SEO的影响
  • 力扣(LeetCode)21
  • 少走弯路,给Java 1~5 年程序员的建议
  • 深入 Nginx 之配置篇
  • 我的业余项目总结
  • 追踪解析 FutureTask 源码
  • gunicorn工作原理
  • UI设计初学者应该如何入门?
  • !!Dom4j 学习笔记
  • #Z0458. 树的中心2
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (二)Pytorch快速搭建神经网络模型实现气温预测回归(代码+详细注解)
  • (二开)Flink 修改源码拓展 SQL 语法
  • (附源码)ssm教师工作量核算统计系统 毕业设计 162307
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (免费领源码)Java#Springboot#mysql农产品销售管理系统47627-计算机毕业设计项目选题推荐
  • (十二)python网络爬虫(理论+实战)——实战:使用BeautfulSoup解析baidu热搜新闻数据
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转载)虚幻引擎3--【UnrealScript教程】章节一:20.location和rotation
  • .net解析传过来的xml_DOM4J解析XML文件
  • .NET开发者必备的11款免费工具
  • @javax.ws.rs Webservice注解
  • [ 2222 ]http://e.eqxiu.com/s/wJMf15Ku
  • [ JavaScript ] JSON方法