.NET如何在运行时重新加载程序集

5

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

2个回答

6

我使用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,但看起来很有前途! - PassetCronUs

3
一般来说,您不能在同一个AppDomain中重新加载程序集。您可以动态创建一个并加载它(它将永远存在于您的AppDomain中),或者加载另一个几乎相同但不完全相同的副本,但一旦程序集进入AppDomain,它就无法移动。
想象一下,一个库程序集定义了SomeType,而您的客户端代码刚刚创建了一个实例。如果您卸载该库,这个实例应该怎么办?如果库在另一个AppDomain中,则客户端将使用具有明确定义行为(在MarshalByRefObject中)的代理(随着域卸载而变成僵尸并永远抛出异常)。支持卸载任意类型会使运行时变得非常复杂、不可预测或两者兼而有之。
至于Unity,请参见此讨论。引用:
“程序集重新加载”听起来像某种快速更新检查,但实际上整个脚本环境都会重新加载。这将破坏托管代码中的所有内容。 Unity可以通过使用其序列化系统从中恢复。 Unity在重新加载之前序列化整个场景,然后重新创建和反序列化整个场景。当然,只有可以序列化的内容才能“幸存”这个过程。

好的,我已经按照你第一段所说的做了。我使用字节数组来加载程序集,我也可以用相同的信息加载另一个程序集。但问题在于另一个程序集仍然占用着内存。它占用了多少内存?如果这不可能,Unity是如何做到的呢?它是将旧程序集保留在内存中吗? - KooKoo
你可以从一个独立的域中加载/卸载程序集。参考链接 - Erik Philips
@archnae 这正是我昨晚一直在寻找的。非常感谢你的帮助。 - KooKoo
@ErikPhilips 谢谢。但我的问题是关于不使用MarshalByRefObject的方法。 - KooKoo
你可能仍然需要一些MarshalByRefObjects - Serializable模型将存在于可重新加载的应用程序域中,在重新加载时进行备份和恢复,但它需要一些与外部世界通信的方式 - 因此将会有一些ModelManager:MarshalByRefObject,坐在域中并执行备份/恢复/事件以及模型所需的所有外部事物。 - archnae
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接