据我了解,您希望执行以下操作:
- 将程序集从磁盘加载到内存中,以便使用其中的数据或调用其中的代码
- 以后可以卸载程序集
- 避免将程序集锁定在磁盘上,这样您就可以修改它而无需退出应用程序(或先卸载程序集)
基本上,您所描述的是一种插件系统,您可以通过使用影子 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 查询找到的文章。