设为首页 加入收藏

TOP

使用 .NET Core 3.0 的 AssemblyLoadContext 实现插件热加载(五)
2019-10-09 19:59:07 】 浏览:177
Tags:使用 .NET Core 3.0 AssemblyLoadContext 实现 插件 加载
;{dllPath}.old"); } var sourceFiles = Directory.EnumerateFiles( _pluginDirectory, "*.cs", SearchOption.AllDirectories); var compilationOptions = new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Debug); var references = _defaultAssemblies .Select(assembly => assembly.Location) .Where(path => !string.IsNullOrEmpty(path) && File.Exists(path)) .Select(path => MetadataReference.CreateFromFile(path)) .ToList(); var syntaxTrees = sourceFiles .Select(p => CSharpSyntaxTree.ParseText(File.ReadAllText(p))) .ToList(); var compilation = CSharpCompilation.Create(_pluginName) .WithOptions(compilationOptions) .AddReferences(references) .AddSyntaxTrees(syntaxTrees); var emitResult = compilation.Emit(dllPath); if (!emitResult.Success) { throw new InvalidOperationException(string.Join("\r\n", emitResult.Diagnostics.Where(d => d.WarningLevel == 0))); } //return _context.LoadFromAssemblyPath(Path.GetFullPath(dllPath)); using (var stream = File.OpenRead(dllPath)) { var assembly = _context.LoadFromStream(stream); return assembly; } }

这个方法会调用 Roslyn 编译插件代码到 DLL,并使用自定义的 AssemblyLoadContext 加载编译后的 DLL。首先它需要删除原有的 DLL 文件,因为卸载程序集有延迟,原有的 DLL 文件在 Windows 系统上很可能会删除失败并提示正在使用,所以需要先重命名并在下次删除。接下来它会查找插件文件夹下的所有 C# 源代码,用 CSharpSyntaxTree 解析它们,并用 CSharpCompilation 编译,编译时引用的程序集列表是构造函数中取得的默认 AssemblyLoadContext 中的程序集列表 (包括宿主程序集,这样插件代码才可以使用 IPlugin 接口)。编译成功后会使用自定义的 AssemblyLoadContext 加载编译后的 DLL 以支持卸载。

这段代码中有两个需要注意的部分,第一个部分是 Roslyn 编译失败时不会抛出异常,编译后需要判断 emitResult.Success 并从 emitResult.Diagnostics 找到错误信息;第二个部分是加载插件程序集必须使用 AssemblyLoadContext.LoadFromStream 从内存数据加载,如果使用 AssemblyLoadContext.LoadFromAssemblyPath 那么下次从同一个路径加载时仍然会返回第一次加载的程序集,这可能是 .NET Core 3.0 的实现问题并且有可能在以后的版本修复。

PluginController.GetInstance

private IPlugin GetInstance()
{
    var instance = _instance;
    if (instance != null && !_changed)
        return instance;

    lock (_reloadLock)
    {
        instance = _instance;
        if (instance != null && !_changed)
            return instance;

        UnloadPlugin();
        _context = new AssemblyLoadContext(
            name: $"Plugin-{_pluginName}", isCollectible: true);

        var assembly = CompilePlugin();
        var pluginType = assembly.GetTypes()
            .First(t => typeof(IPlugin).IsAssignableFrom(t));
        instance = (IPlugin)Activator.CreateInstance(pluginType);

        _instance = instance;
        _changed = false;
    }

    return instance;
}?

这个方法是获取最新插件实例的方法,如果插件实例已创建并且文件没有改变,则返回已有的实例,否则卸载原有的插件、重新编译插件、加载并生成实例。注意 AssemblyLoadContext 类型在 netstandard (包括 2.1) 中是 abstract 类型,不能直接创建,只有 netcoreapp3.0 才可以直接创建 (目前也只有 .NET Core 3.0 支持这项机制),如果需要支持可回收则创建时需要设置 isCollectible 参数为 true,因为支持可回收会让 GC 扫描对象时做一些额外的工作所以默认不启用。

PluginController.GetMessage

public string GetMessage()
{
    return GetInstance().GetMessage();
}

这个方法是代理方法,会获取最新的插件实例并转发调用参数与结果,如果 IPlugin 有其他方法也可以像这个方法一样写。

PluginController.Dispose

public void Dispose()
{
    UnloadPlugin();
    _watcher?.Dispose();
    _watcher = null;
}

这个方法支持主动释放 PluginController,会卸载已加载的插件并且停止监听插件文件。因为 PluginController 没有直接管理非托管资源,并且 AssemblyLoadContext 的析构函数 会触发卸载,所以 PluginController 不需要提供析构函数。

主函数代码

static void Main(string[] args)
{
    using (var controller = new PluginController("MyPlugin", "../guest"
首页 上一页 2 3 4 5 下一页 尾页 5/5/5
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇.NET开发者必须学习.NET Core 下一篇.netcore+vue+elementUI 前后端分..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目