简体   繁体   English

如何动态加载和卸载(重新加载).dll 程序集

[英]How to dynamically load and unload (reload) a .dll assembly

I'm developing a module for an external application, which is a dll that is loaded.我正在为外部应用程序开发一个模块,它是一个加载的 dll。

However in order to develop, you have to restart the application to see the results of your code.但是,为了进行开发,您必须重新启动应用程序才能查看代码的结果。

We have built a piece of code that loads a dll dynamically from a startassembly:我们已经构建了一段从 startassembly 动态加载 dll 的代码:

startassembly开始组装

var dllfile = findHighestAssembly(); // this works but omitted for clarity
Assembly asm = Assembly.LoadFrom(dllFile);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);

Effectively we have a solution with a startassembly which will be static and a test assembly which will be invoked dynamically, which allows us to swap the assembly during runtime.实际上,我们有一个包含静态启动程序集和动态调用的测试程序集的解决方案,这允许我们在运行时交换程序集。

The problem This piece of code will load a new dll every time and search for the highest version at the end of the assembly name.问题这段代码每次都会加载一个新的dll,并在程序集名称的末尾搜索最高版本。 eg test02.dll will be loaded instead of test01.dll, because the application locks both startassemly.dll as well as test01.dll.例如,将加载 test02.dll 而不是 test01.dll,因为应用程序会同时锁定 startassemly.dll 和 test01.dll。 Now we have to edit the properties > assembly name all the time.现在我们必须一直编辑属性 > 程序集名称。

I want to build a new dll while the main application still runs.我想在主应用程序仍在运行时构建一个新的 dll。 However for now I get the message但是现在我收到消息

The process cannot access the file test.dll because it is being used by another process该进程无法访问文件 test.dll,因为它正被另一个进程使用

I have read that you can unload a .dll using AppDomain s however the problem is that I don't know how to properly unload an AppDomain and where to do this.我读过您可以使用AppDomain卸载 .dll 但问题是我不知道如何正确卸载 AppDomain 以及在哪里执行此操作。

The goal is to have to reload the new test.dll everytime the window is re-opened (by a button click from the main application).目标是每次重新打开窗口时都必须重新加载新的 test.dll(通过从主应用程序单击按钮)。

You cannot unload a single assembly, but you can unload an Appdomain.您不能卸载单个程序集,但可以卸载 Appdomain。 This means you need to create an app domain and load the assembly in the App domain.这意味着您需要创建一个应用程序域并在应用程序域中加载程序集。

Exmaple:例子:

var appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
{
    ApplicationName = "MyAppDomain",
    ShadowCopyFiles = "true",
    PrivateBinPath = "MyAppDomainBin",
});

ShadowCopyFiles property will cause the .NET runtime to copy dlls in "MyAppDomainBin" folder to a cache location so as not to lock the files in that path. ShadowCopyFiles 属性将导致 .NET 运行时将“MyAppDomainBin”文件夹中的 dll 复制到缓存位置,以免锁定该路径中的文件。 Instead the cached files are locked.相反,缓存的文件被锁定。 For more information refer to article about Shadow Copying Assemblies有关更多信息,请参阅有关卷影复制程序集的文章

Now let's say you have an class you want to use in the assembly you want to unload.现在假设您有一个要在要卸载的程序集中使用的类。 In your main app domain you call CreateInstanceAndUnwrap to get an instance of the object在您的主应用程序域中,您调用CreateInstanceAndUnwrap来获取对象的实例

_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");

However, and this is very important , "Unwrap" part of CreateInstanceAndUnwrap will cause the assembly to be loaded in your main app domain if your class does not inherit from MarshalByRefObject .但是,这非常重要,如果您的类不是从MarshalByRefObject继承的,那么CreateInstanceAndUnwrap “Unwrap”部分将导致程序集加载到您的主应用程序域中。 So basically you achieved nothing by creating an app domain.所以基本上你通过创建一个应用程序域什么也没做。

To solve this problem, create a 3rd Assembly containing an Interface that is implemented by your class.要解决此问题,请创建一个包含由您的类实现的接口的第三个程序集。

For example:例如:

public interface IMyInterface
{
    void DoSomething();
}

Then add reference to the assembly containing the interface in both your main application and your dynamically loaded assembly project.然后在主应用程序和动态加载的程序集项目中添加对包含接口的程序集的引用。 And have your class implement the interface, and inherit from MarshalByRefObject .并让您的类实现接口,并从MarshalByRefObject继承。 Example:例子:

public class MyClass : MarshalByRefObject, IMyInterface
{
    public void DoSomething()
    {
        Console.WriteLine("Doing something.");
    }
}

And to get a reference to your object:并获取对您的对象的引用:

var myObj = (IMyInterface)_appDomain.CreateInstanceAndUnwrap("MyAssemblyName", "MyNameSpace.MyClass");

Now you can call methods on your object, and .NET Runtime will use Remoting to forward the call to the other domain.现在您可以调用对象上的方法,.NET 运行时将使用远程处理将调用转发到其他域。 It will use Serialization to serialize the parameters and return values to and from both domains.它将使用序列化来序列化参数并从两个域返回值。 So make sure your classes used in parameters and return values are marked with [Serializable] Attribute.因此,请确保您在参数和返回值中使用的类标有[Serializable]属性。 Or they can inherit from MarshalByRefObject in which case the you are passing a reference cross domains.或者它们可以从MarshalByRefObject继承,在这种情况下,您传递的是跨域引用。

To have your application monitor changes to the folder, you can setup a FileSystemWatcher to monitor changes to the folder "MyAppDomainBin"要让您的应用程序监控对文件夹的更改,您可以设置FileSystemWatcher来监控对文件夹“MyAppDomainBin”的更改

var watcher = new FileSystemWatcher(Path.GetFullPath(Path.Combine(".", "MyAppDomainBin")))
{
    NotifyFilter = NotifyFilters.LastWrite,
};
watcher.EnableRaisingEvents = true;
watcher.Changed += Folder_Changed;

And in the Folder_Changed handler unload the appdomain and reload it again并在Folder_Changed处理程序中卸载 appdomain 并再次重新加载

private static async void Watcher_Changed(object sender, FileSystemEventArgs e)
{
    Console.WriteLine("Folder changed");
    AppDomain.Unload(_appDomain);
    _appDomain = AppDomain.CreateDomain("MyAppDomain", null, new AppDomainSetup
    {
        ApplicationName = "MyAppDomain",
        ShadowCopyFiles = "true",
        PrivateBinPath = "MyAppDomainBin",
    });
}

Then when you replace your DLL, in "MyAppDomainBin" folder, your application domain will be unloaded, and a new one will be created.然后,当您替换 DLL 时,在“MyAppDomainBin”文件夹中,您的应用程序域将被卸载,并创建一个新域。 Your old object references will be invalid (since they reference objects in an unloaded app domain), and you will need to create new ones.您的旧对象引用将无效(因为它们引用了未加载应用程序域中的对象),您需要创建新对象。

A final note: AppDomains and .NET Remoting are not supported in .NET Core or future versions of .NET (.NET 5+).最后一点:.NET Core 或未来版本的 .NET (.NET 5+) 不支持 AppDomains 和 .NET Remoting。 In those version, separation is achieved by creating separate processes instead of app domains.在这些版本中,分离是通过创建单独的进程而不是应用程序域来实现的。 And using some sort of messaging library to communicate between processes.并使用某种消息传递库在进程之间进行通信。

what you're trying to do in the code you posted is unload the default app domain which your program will run in if another isn't specified.您在发布的代码中尝试做的是卸载默认应用程序域,如果未指定其他应用程序域,您的程序将在该域中运行。 What you're probably wanting is to load a new app domain, load the assembly into that new app domain, and then unloaded the new app domain when the user destroys the page.您可能想要的是加载一个新的应用程序域,将程序集加载到该新的应用程序域中,然后在用户销毁页面时卸载新的应用程序域。

https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7 https://docs.microsoft.com/en-us/dotnet/api/system.appdomain?view=netframework-4.7

the reference page above should give you a working example of all of this.上面的参考页面应该为您提供所有这些的工作示例。

Not the way forward in .NET Core 3 and .NET 5+不是 .NET Core 3 和 .NET 5+ 的前进方向

Some of the answers here assume working with .NET Framework.这里的一些答案假设使用 .NET Framework。 In .NET Core 3 and .NET 5+, the correct way to load assemblies (with ability to unload them) in the same process is with AssemblyLoadContext.在 .NET Core 3 和 .NET 5+ 中,在同一进程中加载​​程序集(能够卸载它们)的正确方法是使用 AssemblyLoadContext。 Using AppDomain as a way to isolate assemblies is strictly for .NET Framework.使用 AppDomain 作为隔离程序集的方法严格适用于 .NET Framework。

.NET Core 3 and 5+, give you two possible ways to load dynamic assemblies (and potentially unload): .NET Core 3 和 5+,为您提供了两种加载动态程序集(并可能卸载)的可能方法:

  1. Load another process and load your dynamic assemblies there.加载另一个进程并在那里加载动态程序集。 Then use an IPC messaging system of your choosing to send messages between the processes.然后使用您选择的 IPC 消息系统在进程之间发送消息。
  2. Use AssemblyLoadContext to load them in the same process.使用AssemblyLoadContext在同一进程中加载​​它们。 Note that the scope does NOT provide any kind of security isolation or boundaries within the process.请注意,范围不提供进程内的任何类型的安全隔离或边界。 In other words, code loaded in a separate context is still able to invoke other code in other contexts within the same process.换句话说,加载在单独上下文中的代码仍然能够调用同一进程内其他上下文中的其他代码。 If you want to isolate the code because you expect to be loading assemblies that you can't fully trust, then you need to load it in a completely separate process and rely on IPC.如果您希望隔离代码,因为您希望加载您不能完全信任的程序集,那么您需要在一个完全独立的进程中加载​​它并依赖 IPC。

An article explaining AssemblyLoadContext is here .一篇解释 AssemblyLoadContext 的文章在这里

Plugin unloadability discussed here .插件unloadability讨论在这里

Many people who want to dynamically load DLLs are interested in the Plugin pattern.许多想要动态加载 DLL 的人都对插件模式感兴趣。 The MSDN actually covers this particular implementation here: https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support MSDN 实际上在这里涵盖了这个特定的实现: https : //docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support

Here is an example for loading and unloading an AppDomain.这是加载和卸载 AppDomain 的示例。
In my example I have 2 Dll's: DynDll.dll and DynDll1.dll.在我的示例中,我有 2 个 Dll:DynDll.dll 和 DynDll1.dll。
Both Dll's have the same class DynDll.Class and a method Run (MarshalByRefObject is required):两个 Dll 都有相同的类 DynDll.Class 和方法 Run(需要 MarshalByRefObject):

public class Class : MarshalByRefObject
{
    public int Run()
    {
        return 1; //DynDll1 return 2
    }
}

Now you can create a dynamic AppDomain and load a Assembly:现在您可以创建动态 AppDomain 并加载程序集:

AppDomain loDynamicDomain = null;
try
{
    //FullPath to the Assembly
    string lsAssemblyPath = string.Empty;
    if (this.mbLoad1)
        lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll1.dll");
    else
        lsAssemblyPath = Path.Combine(Application.StartupPath, "DynDll.dll");
    this.mbLoad1 = !this.mbLoad1;

    //Create a new Domain
    loDynamicDomain = AppDomain.CreateDomain("DynamicDomain");
    //Load an Assembly and create an instance DynDll.Class
    //CreateInstanceFromAndUnwrap needs the FullPath to your Assembly
    object loDynClass = loDynamicDomain.CreateInstanceFromAndUnwrap(lsAssemblyPath, "DynDll.Class");
    //Methode Info Run
    MethodInfo loMethodInfo = loDynClass.GetType().GetMethod("Run");
    //Call Run from the instance
    int lnNumber = (int)loMethodInfo.Invoke(loDynClass, new object[] { });
    Console.WriteLine(lnNumber.ToString());
}
finally
{
    if (loDynamicDomain != null)
        AppDomain.Unload(loDynamicDomain);
}

Here is an idea, instead of loading the DDL directly (as is), let the application rename it, then load the renamed ddl (eg test01_active.dll).这里有一个想法,不是直接加载 DDL(按原样),而是让应用程序重命名它,然后加载重命名的 ddl(例如 test01_active.dll)。 Then, just check for the original file (test01.dll) before loading the assembly and if exists, just delete the current one(test01_active.dll) and then rename the updated version then reload it, and so on.然后,只需在加载程序集之前检查原始文件(test01.dll),如果存在,只需删除当前文件(test01_active.dll),然后重命名更新版本,然后重新加载它,依此类推。

Here is a code shows the idea :这是一个代码显示的想法:

const string assemblyDirectoryPath = "C:\\bin";
const string assemblyFileNameSuffix = "_active";

var assemblyCurrentFileName     = "test01_active.dll";
var assemblyOriginalFileName    = "test01.dll";

var originalFilePath = Path.Combine(assemblyDirectoryPath, assemblyOriginalFileName);
var currentFilePath  = Path.Combine(assemblyDirectoryPath, assemblyCurrentFileName);

if(File.Exists(originalFilePath))
{
    File.Delete(currentFilePath);
    File.Move(originalFilePath, currentFilePath);
}

Assembly asm = Assembly.LoadFrom(currentFilePath);
Type type = asm.GetType("Test.Program");
MethodInfo methodInfo = type.GetMethod("Run");
object[] parametersArray = new object[] { };
var result = methodInfo.Invoke(methodInfo, parametersArray);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM