【问题标题】:How to load an assembly without using Assembly.Load?如何在不使用 Assembly.Load 的情况下加载程序集?
【发布时间】:2009-10-29 19:35:18
【问题描述】:

是否可以通过将程序集流式传输到内存来做到这一点?如果是这样,如何做到这一点?

原因:

我不想锁定已加载的 dll。我希望能够动态加载,更改代码,再次编译和重新加载

【问题讨论】:

  • 您为什么需要这样做?为什么不能使用 Assembly.Load?
  • 那你的问题改成问那个,我来回答。
  • 在 Stack Overflow 上获得好的答案的好方法,实际上,几乎在其他任何地方,都是尽可能多地解释问题。如果您的问题是“我怎样才能在不伤害自己的情况下射中自己的脚”,那么您总是会遇到试图阻止您这样做的人,而不是回答您真正的问题是“我该怎么做治愈我膝盖以下的痒”。因此,如果可能,请始终解释您提出问题的潜在动机,因为这可能会为您提供您没有想到的替代解决方案。
  • @Lasse V. Karlsen:我同意你的观点,但我有时会遇到的问题是,如果你提供太多有关问题的信息,没有人会阅读和回答,因为问题变得令人生畏!
  • 我同意 Josn 的观点,提供太多细节会让一些人望而却步。

标签: c# .net reflection assemblies


【解决方案1】:

据我了解,您希望执行以下操作:

  1. 将程序集从磁盘加载到内存中,以便使用其中的数据或调用其中的代码
  2. 以后可以卸载程序集
  3. 避免将程序集锁定在磁盘上,这样您就可以修改它而无需退出应用程序(或先卸载程序集)

基本上,您所描述的是一种插件系统,您可以通过使用影子 dll 和应用程序域来做到这一点。

首先,为了卸载程序集,而不仅仅是退出应用程序,您需要将该程序集加载到单独的应用程序域中。您应该能够在网上找到有关如何做到这一点的好教程。

这是一个Google query,它应该为您提供一些起始文章。

其次,为了避免将程序集锁定在磁盘上,这很简单,只需先对其进行复制,然后加载副本而不是原始文件。当然,您将锁定该副本,但该副本只是您的应用程序的一个临时文件,因此无论如何都不应该有兴趣修改该文件。这会使原始文件处于解锁状态且可修改。

您应该尝试使用阴影而不是使用 Assembly.Load 的重载,如果您有多个要加载和替换的程序集,则可以从字节数组加载程序集。

例如,如果您的插件程序集 A.dll 依赖于第二个程序集 B.dll,并且您在调用 Assembly.Load 之前使用字节数组技巧将 A.dll 加载到内存中,则您需要处理程序集解析调用在您的应用程序域中(您可以在需要加载程序集时被告知,并“帮助”加载过程),或者您需要确保首先加载 B.dll 与加载 A.dll 的方式相同,否则加载 A内存中的 .dll 会自动从磁盘加载 B.dll。


以下是有关使用单独应用程序域的更多信息。

当您创建另一个应用程序域时,通过在 .NET 中使用 AppDomain 类,您将在内存中创建一个单独的隔间,您可以在其中运行代码。它确实与您的主应用程序域分开,并且只有一个穿过墙壁的小孔将它们分开。

通过这个洞你可以传递消息,比如方法调用和数据。

在构建新的应用程序域后,您在其中加载一个或多个程序集。通常,如果您要加载的程序集是为这种类型的加载而构建的,则您将加载 1,如果没有,则加载 2(更多内容见下文)。

加载程序集后,您可以在另一个应用程序域中构造一个或多个对象,这样第一个应用程序域就可以与这些对象通信。这些对象需要从MarshalByRefObject 继承,这是一个允许一些魔法发生的类。

基本上发生的事情是这样的。在该其他应用程序域内,创建加载到该应用程序域的类型的对象。这种类型来自MarshalByRefObject。构造这个对象的请求来自第一个应用程序域,在这个应用程序域内,构造了一个代理对象,它看起来像但不是在另一个应用程序域中创建的同一对象。代理通过那个洞与另一个对象对话。

所以现在您有两个应用程序域和两个对象,每侧一个,并且对象相互通信。

使用此设置,稍后您可以切断对象之间的连接,然后卸载其他应用程序域,这基本上会破坏该隔间。然后,如果您愿意,您可以构建一个新的第二个应用程序域,然后重新开始,实际上是再次从磁盘重新加载程序集。

要注意的一件事是您通过此孔的数据。如果您通过该孔的任何数据是在您加载的程序集(您的插件或扩展程序集)中声明的对象,那么您不仅会将该对象返回到您的主应用程序域,您的主应用程序域也将将该程序集加载到其自己的域中,因此无法在重新加载后与您的第二个应用程序域正确通信。

因此,请确保不要这样做,传递本地类型或在要替换的程序集之外定义的类型。

我提到您可能想要加载至少两个程序集。这背后的原因是,如果您要构造其对象的类型,在您要加载的程序集中声明的类型不是从MarshalByRefObject 下降的,那么通过该孔传递类型的问题再次出现起来,你也会将程序集加载到你的主域中。处理此问题的典型方法是拥有某种插件管理器类,它确实源自MarshalByRefObject,并让该管理器位于另一个域中并与其他类型进行对话。这避免了将类型通过该孔的问题。

我已经在这里闲逛了一段时间,所以我就不说了,但是有了这些信息,您应该能够更容易地理解和使用通过该 Google 查询找到的文章。

【讨论】:

  • +1 确实是实现此目的的唯一方法。卸载“动态”AppDomain 可以避免典型的内存泄漏。
  • 谢谢,当您将程序集加载到单独的应用程序域时,我的应用程序可以访问它吗? appdomains 有什么帮助? (我不知道)。这仍然会锁定dll吗?因为一直复制程序集似乎是一种肮脏的黑客行为。
  • @Joan 根本不是黑客——复制到内存正是避免将副本锁定在磁盘上的方法。 AppDomains 绝对可以相互交谈 - 只是比在同一个 AppDomain 中交谈的开销更大。
  • 复制到内存?好的,我因为这部分而感到困惑:“其次,为了避免将程序集锁定在磁盘上” 另外你所说的开销,装箱,拆箱是什么意思?
  • 您仍然可以通过代理对象访问您的对象和方法(marshal by ref)。您可以通过反射 API 调用静态方法等。
【解决方案2】:

这可以通过使用字节数组的负载重载来完成。您需要在加载前读取汇编字节,它不会锁定文件:

byte[] readAllBytes = File.ReadAllBytes("path");
Assembly assembly = Assembly.Load(readAllBytes);

【讨论】:

  • Henk,你的意思是它不适合卸载?再次执行此操作不会替换现有加载的类?
  • 从字节加载时,它每次都会加载一个副本。您将获得一个新的程序集引用,您可以在其上工作,但它不会卸载旧的程序集。这取决于您实际要使用它做什么,因此需要权衡取舍。
  • @Elisha 现在在 2014 年,它似乎在 Assembly.Load() 的 XML cmets 中已被弃用 - 是否有面向未来的解决方案?
【解决方案3】:

您可以创建程序集的临时副本并使用 Assembly.Load 加载该副本。在原始文件上放置一个文件监视器,并在文件监视器检测到更改时卸载/重新加载更新程序集的临时副本。

【讨论】:

    【解决方案4】:

    如果您从源代码即时编译 DLL,您甚至不必制作副本,而是重新编译过程应该如下:

    1) 销毁加载了程序集的应用程序域,从而有效地解锁 DLL。

    2) 重新编译脚本。

    3) 为插件重新创建应用程序域,并在其中加载新的程序集。

    【讨论】:

    • 不利的一面是,在 1) 和 3) 之间至少有几秒钟的时间间隔,您的应用程序不可用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多