【问题标题】:Load and unload a dll dynamically into my project using AppDomain使用 AppDomain 将 dll 动态加载和卸载到我的项目中
【发布时间】:2018-11-08 08:39:20
【问题描述】:

我想在当前项目的单独解决方案中动态使用来自不同项目的类。我认为解决方案是将dll加载到我的项目中。我使用下面的代码来完成我的任务,并且成功了。

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;
      }
}

但是,我现在的问题是我想卸载 dll,我不能这样做,因为在 Assembly 中没有卸载方法。我找到的解决方案是我必须使用 AppDomain 加载程序集,然后再卸载它。

现在这是我的主要问题。我不断收到FileNotFoundException。这是我的代码:

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
}

最后一行出现以下异常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.

我已经尝试了这个link 的解决方案,但没有什么对我有用...我一直遇到同样的异常。之后我想卸载域,但我无法解决加载 dll 的第一个问题。如何修复我的代码?

编辑

当我将预期的 dll 复制到项目的同一个 bin 文件夹中时,它可以工作。但是,我不想在我的项目中复制 dll。有没有办法从它的路径加载它而不将它复制到我的 bin 文件夹?

【问题讨论】:

  • 注意:DLL一旦加载就无法卸载
  • 是的,这就是为什么我需要使用AppDomain,所以我可以在完成后卸载它。
  • 看起来你帮的太多了,你不想自己做 AppDomainSetup。使用 Environment.CurrentDirectory 非常麻烦,它是一个丑陋的全局变量,在 SO 问题中大约 100% 的时间其值设置错误。只需使用 CreateDomain(string) 重载。
  • 你的意思是保持我相同的逻辑,但删除 AppDomainSetup 并只使用 CreateDomain(string)?我这样做了,但总是遇到同样的异常。
  • 您可能必须从 AppDomain 卸载和删除才能卸载程序集。在blog.vcillusion.co.in/… 上尝试示例希望它有所帮助!

标签: c# assemblies appdomain


【解决方案1】:

您在代理域中定义了一个GetAssembly 方法,它将加载的Assembly 拉入主域。这使整个概念变得毫无意义,因为即使您卸载代理域,您的主域最终也会被加载的程序集污染。

无需返回程序集,只需在您的代理域中使用它即可。如果您想将一些信息推回主域,您必须传递简单的可序列化类型(或从MarshalByRefObject 派生的远程对象),以便主域保持干净。

你应该这样做:

// 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;
}

你的代理应该是这样的:

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.")
    }
}

用法:

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);

最后是FileNotFoundException 本身:

AppDomain 中,您只能加载程序集,这些程序集位于主域的同一文件夹或子文件夹中。在设置对象中使用它而不是 Environment.CurrentDirectory

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

如果您真的想从任何位置加载程序集,请将其加载为byte[]

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

【讨论】:

  • 首先感谢您的回复,对于我没有完全理解这一点,我深表歉意。关于您的第一部分,我在哪里指定我需要在您的代码中的目标 dll?而且我不需要发送任何数据,只调用某个void函数,我还需要sendData(object)吗?还是您的意思是我应该在 DomainHost 类的 sendData 函数中加载我的 dll 并在 DoWork() 的调用中指定 dll 路径?在您的最后一部分中,我按照您所说的替换了 AppDomainSetup,但仍然遇到相同的异常。
  • 我在你的代码中解决了这个问题。我不得不将dll复制到我项目的bin文件夹中,但是我正确使用它并且之后能够释放它,这对我来说是一个很大的突破。我可以这样解决问题。但是,有没有办法从它的位置加载 dll 而不必将它复制到我的 bin 文件夹中?
  • 我很高兴它对你有用。如果您不需要任何反馈,当然不需要主持人。或者从DoWork 返回一个布尔值或其他内容就足够了。位置:见我的最后一行。您可以将 dll 加载为字节数组,可以将其加载为 Assembly。当然,如果有的话,您可能需要处理依赖程序集。
  • 使用最后一行使我无法在之后释放 dll。此外,Environment.CurrentDirectoryAppDomain.CurrentDomain.BaseDirectory 在调试中给出了相同的路径。因此,制作与 dll 相同的 FileNotFoundException 不在 Application bin 文件夹中,我必须再次将其复制到那里以使其工作。
  • “使用最后一行让我之后无法释放 dll” - 你无法卸载 AppDomain 或者你是什么意思?
猜你喜欢
  • 1970-01-01
  • 2012-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-12
  • 1970-01-01
  • 1970-01-01
  • 2010-09-10
相关资源
最近更新 更多