.net 在运行时重新加载程序集

Posted

技术标签:

【中文标题】.net 在运行时重新加载程序集【英文标题】:.net reload assembly at runtime 【发布时间】:2018-04-24 05:03:15 【问题描述】:

我搜索了很多关于在 .NET 中运行时重新加载程序集的信息。我能找到的唯一方法是使用另一个 AppDomain。但这使事情变得非常复杂。在我的情况下这几乎是不可能的,因为将在运行时加载的程序集中的类不会从 MarshalByRefObject 继承。我看过 Unity 游戏引擎。编辑器在运行时构建组件并仅使用已编译的程序集。怎么可能?

【问题讨论】:

【参考方案1】:

我已经使用 MEF 完成了这项工作。我不确定它是否适合您,但效果很好。然而,即使使用 MEF,它也有些复杂。

在我的情况下,我从特定文件夹加载所有 dll。

这些是设置类。

public static class SandBox

    public static AppDomain CreateSandboxDomain(string name, string path, SecurityZone zone)
    
        string fullDirectory = Path.GetFullPath(path);
        string cachePath = Path.Combine(fullDirectory, "ShadowCopyCache");
        string pluginPath = Path.Combine(fullDirectory, "Plugins");

        if (!Directory.Exists(cachePath))
            Directory.CreateDirectory(cachePath);

        if (!Directory.Exists(pluginPath))
            Directory.CreateDirectory(pluginPath);


        AppDomainSetup setup = new AppDomainSetup
        
            ApplicationBase = fullDirectory,
            CachePath = cachePath,
            ShadowCopyDirectories = pluginPath,
            ShadowCopyFiles = "true"
        ;

        Evidence evidence = new Evidence();
        evidence.AddHostEvidence(new Zone(zone));
        PermissionSet permissions = SecurityManager.GetStandardSandbox(evidence);

        return AppDomain.CreateDomain(name, evidence, setup, permissions);
    


public class Runner : MarshalByRefObject

    private CompositionContainer _container;
    private DirectoryCatalog _directoryCatalog;
    private readonly AggregateCatalog _catalog = new AggregateCatalog();


    public bool CanExport<T>()
    
        T result = _container.GetExportedValueOrDefault<T>();
        return result != null;
    

    public void Recompose()
    
        _directoryCatalog.Refresh();
        _container.ComposeParts(_directoryCatalog.Parts);
    

    public void RunAction(Action codeToExecute)
    
        MefBase.Container = _container;
        codeToExecute.Invoke();
    

    public void CreateMefContainer()
    
        RegistrationBuilder regBuilder = new RegistrationBuilder();
        string pluginPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
        _directoryCatalog = new DirectoryCatalog(pluginPath, regBuilder);
        _catalog.Catalogs.Add(_directoryCatalog);

        _container = new CompositionContainer(_catalog, true);
        _container.ComposeExportedValue(_container);
        Console.WriteLine("exports in AppDomain 0", AppDomain.CurrentDomain.FriendlyName);
    

这是实际代码。

AppDomain domain = SandBox.CreateSandboxDomain($"Sandbox Domain_currentCount", directoryName, SecurityZone.MyComputer);
            foreach (FileInfo dll in currentDlls)
            
                string path = Path.GetFullPath(Path.Combine(directoryName, dll.Name));
                if (!File.Exists(path))
                    File.Copy(dll.FullName, Path.Combine(directoryName, dll.Name), true);

                domain.Load(typeof(Runner).Assembly.FullName);
            

您可以通过这样做来取回域。

Runner runner = (Runner) domain.CreateInstanceAndUnwrap(typeof(Runner).Assembly.FullName, typeof(Runner).FullName);
runner.CreateMefContainer(); // or runner.Recompose();

你需要像这样调用你的代码。

runner.RunAction(() =>
                
                    IRepository export = MefBase.Resolve<IRepository>();
                    export?.Get("123");
                    Console.WriteLine("Executing 0", export);
                );

【讨论】:

不确定这是否适用于 Unity,但看起来很有希望!【参考方案2】:

一般来说,您不能在同一个 AppDomain 中重新加载程序集。您可以动态创建一个并加载它(它将永远位于您的 AppDomain 中),您可以加载另一个几乎但不完全相同的程序集副本,但是一旦程序集在 AppDomain 中,它就会卡住。

想象一个库程序集定义了 SomeType 并且您的客户端代码刚刚创建了一个实例。如果你卸载库,这个实例会发生什么?如果该库位于另一个 AppDomain 中,则客户端将使用具有明确定义(在 MarshalByRefObject 中)行为的代理(在域卸载后变得僵尸并永远抛出异常)。支持卸载任意类型会使运行时变得异常复杂、不可预测或两者兼而有之。

关于 Unity,请参阅 this discussion。引用:

“程序集重新加载”听起来像是某种快速更新检查,但实际上整个脚本环境会重新加载。这将摧毁管理土地上的一切。 Unity 可以通过使用它的序列化系统从中恢复。 Unity 在重新加载之前序列化整个场景,然后重新创建所有内容并反序列化整个场景。当然,只有可以序列化的东西才能“存活”这个过程。

【讨论】:

好吧。我实际上已经完成了您在第一段中所说的内容。我使用字节数组加载程序集。我可以使用相同的信息加载另一个程序集。但问题是另一个程序集仍在内存中。需要多少内存?如果不可能,团结如何做到这一点?是否将旧程序集保留在内存中? 见上文 - Unity 不这样做,它只是假装 :) You can load/unload assemblies from a separate domain. @archnae 这正是我昨晚以来一直在寻找的。感谢您的大力帮助。 @ErikPhilips 谢谢。但我的问题是关于不使用 MarshalByRefObject 的方法。

以上是关于.net 在运行时重新加载程序集的主要内容,如果未能解决你的问题,请参考以下文章

在尝试加载程序集 ID 65537 时 Microsoft .NET Framework 出错.服务器可能资源不

如何为 .NET 应用程序域重新加载程序集?

我可以在运行时加载 .NET 程序集并实例化只知道名称的类型吗?

在运行时加载、使用和卸载程序集

如何在.NET 运行时将文件夹添加到程序集搜索路径?

.NET 3.5 未能加载文件或程序集“Microsoft.Web.Infrastructure”(解决后追加50-200悬赏)