3

.NET Core中插件式开发实现

 3 years ago
source link: https://www.cnblogs.com/cwsheng/p/14828754.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

 之前在文章- AppDomain实现【插件式】开发 中介绍了在 .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 方法只是为了启动卸载。以下目标达成后,卸载完成:

  • 没有线程将程序集中的方法加载到其调用堆栈上的 AssemblyLoadContext 中。
  • 程序集中的任何类型都不会加载到 AssemblyLoadContext,这些类型的实例本身由以下引用:
    • AssemblyLoadContext 外部的引用,弱引用(WeakReference 或 WeakReference<T>)除外。
    • 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未卸载,程序集数量增加。

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

  

374428-20210530204444301-1554634051.png

 四、总结:

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

 源码地址

 参考:https://docs.microsoft.com/zh-cn/dotnet/standard/assembly/unloadability 


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK