简体   繁体   English

使用AppDomain将dll动态加载和卸载到我的项目中

[英]Load and unload a dll dynamically into my project using AppDomain

I want to use a class from a different project in a separate solution in my current project dynamically. 我想动态地在当前项目的单独解决方案中使用来自不同项目的类。 I thought the solution is to load the dll into my project. 我认为解决方案是将dll加载到我的项目中。 I used the following code to do my task and it worked. 我使用下面的代码来完成我的任务,并且可以正常工作。

string dllPath = @"the path of my dll";
var DLL = Assembly.LoadFile(dllPath);
foreach (Type type in DLL.GetExportedTypes())
{
      if (type.Name == "targetClassName")
      {
          var c = Activator.CreateInstance(type);
          try
          {
              type.InvokeMember("myMethod", BindingFlags.InvokeMethod, null, c, new object[] { "Params" });
          }
          catch(Exception ex)
          {
             MessageBox.Show(ex.Message);
          }
          break;
      }
}

However, my problem now is that I want to unload the dll, which I can't do because there is no unload method in Assembly. 但是,我现在的问题是我想卸载dll,但由于Assembly中没有卸载方法,所以无法执行该操作。 The solution I found is that I must use AppDomain to load the assembly and then unload it. 我发现的解决方案是必须使用AppDomain加载程序集,然后再卸载它。

Now here is my main problem . 现在这是我的主要问题 I keep getting FileNotFoundException . 我不断收到FileNotFoundException Here is my code: 这是我的代码:

public class ProxyDomain : MarshalByRefObject
{
    public Assembly GetAssembly(string assemblyPath)
    {
        try
        {
            return Assembly.LoadFrom(assemblyPath);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(ex.Message);
        }
    }
}

private void BuildButton_Click(object sender, EventArgs e)
{
    string dllPath = @"DllPath";
    string dir = @"directory Path of the dll";
    AppDomainSetup domaininfo = new AppDomainSetup();
    domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
    Evidence adevidence = AppDomain.CurrentDomain.Evidence;
    AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);

    Type Domtype = typeof(ProxyDomain);
    var value = (ProxyDomain)domain.CreateInstanceAndUnwrap(
         Domtype.Assembly.FullName,
         Domtype.FullName);

    var DLL = value.GetAssembly(dllPath);

    // Then use the DLL object as before
}

The last line is making the following exception Could not load file or assembly 'dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. 最后一行产生以下异常Could not load file or assembly 'dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. Could not load file or assembly 'dll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.

I have tried the solutions on this link but nothing is working for me...I keep getting the same exception. 我已经尝试过此链接上的解决方案,但对我来说没有任何帮助...我不断收到相同的异常。 After that I want to unload the domain, but I can't get through with the first problem of loading the dll. 之后,我想卸载域,但无法解决第一个加载dll的问题。 How to fix my code? 如何修复我的代码?

EDIT 编辑

When I copy the intended dll in the same bin folder of my project, it works. 当我将预期的dll复制到项目的同一bin文件夹中时,它可以工作。 However, I don't want to copy the dll in my project. 但是,我不想在项目中复制dll。 Is there a way to load it from its path without copying it to my bin folder? 有没有一种方法可以从其路径加载而不将其复制到我的bin文件夹中?

You define a GetAssembly method in your proxy domain, which pulls the loaded Assembly into the main domain. 您可以在代理域中定义GetAssembly方法,该方法会将已加载的Assembly拖到主域中。 This renders the whole concept pointless, because even if you unload the proxy domain, your main domain will be eventually polluted by the loaded assembly. 这使整个概念毫无意义,因为即使您卸载代理域,您的主域最终也会被加载的程序集污染。

Instead of returning the assembly just use it inside your proxy domain. 无需返回程序集,而只需在您的代理域中使用它即可。 If you want to push back some information into the main domain you must pass simple serializable types (or remote objects derived from MarshalByRefObject ) so the main domain remains clean. 如果要将某些信息推回主域,则必须传递简单的可序列化类型(或从MarshalByRefObject派生的远程对象),以便使主域保持整洁。

This is how you should do it: 这是您应该如何做:

// This class provides callbacks to the host app domain.
// This is optional, you need only if you want to send back some information
public class DomainHost : MarshalByRefObject
{
    // sends any object to the host. The object must be serializable
    public void SendDataToMainDomain(object data)
    {
        Console.WriteLine($"Hmm, some interesting data arrived: {data}");
    }

    // there is no timeout for host
    public override object InitializeLifetimeService() => null;
}

And your proxy should look like this: 您的代理应如下所示:

class AssemblyLoader : MarshalByRefObject
{
    private DomainHost host;

    public void Initialize(DomainHost host)
    {
        // store the remote host here so you will able to use it to send feedbacks
        this.host = host;
        host.SendData("I am just being initialized.")
    }

    // of course, if your job has some final result you can have a return value
    // and then you don't even may need the DomainHost.
    // But do not return any Type from the loaded dll (not mentioning the whole Assembly).
    public void DoWork()
    {
        host.SendData("Work started. Now I will load some dll.");
        // TODO: load and use dll
        host.SendData(42);

        host.SendData("Job finished.")
    }
}

Usage: 用法:

var domain = AppDomain.CreateDomain("SandboxDomain");
var loader = (AssemblyLoader)domain.CreateInstanceAndUnwrap(typeof(AssemblyLoader).Assembly.FullName, typeod(AssemblyLoader).FullName);

// pass the host to the domain (again, this is optional; just for feedbacks)
loader.Initialize(new DomainHost());

// Start the work.
loader.DoWork();

// At the end, you can unload the domain
AppDomain.Unload(domain);

And finally for the FileNotFoundException itself: 最后是FileNotFoundException本身:

In an AppDomain you can only load assemblies, which reside in the same or a subfolder of the main domain. AppDomain您只能加载位于主域的同一文件夹或子文件夹中的程序集。 Use this instead of Environment.CurrentDirectory in the setup object: 在安装程序对象中使用它代替Environment.CurrentDirectory

var setup = new AppDomainSetup
{
    ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
    PrivateBinPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
};

If you really want to load an assembly from any location, load it as a byte[] : 如果您真的想从任何位置加载程序集,请以byte[]加载它:

var dll = Assembly.Load(File.ReadAllBytes(fullPathToDll));

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

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