0

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. 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. The solution I found is that I must use AppDomain to load the assembly and then unload it.

Now here is my main problem. I keep getting 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.

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. How to fix my code?

EDIT

When I copy the intended dll in the same bin folder of my project, it works. However, I don't want to copy the dll in my project. Is there a way to load it from its path without copying it to my bin folder?

  • Note: you can't unload a DLL once it is loaded – DavidG May 29 '18 at 16:11
  • 1
    yes, that's why I need to use the AppDomain, so I can unload it after I finish. – Islam El-gendy May 29 '18 at 16:13
  • Looks like you are helping too much, you don't want to cook your own AppDomainSetup. Using Environment.CurrentDirectory is very troublesome, it is an ugly global variable whose value is set wrong about 100% of the time in SO questions. Just use the CreateDomain(string) overload. – Hans Passant May 29 '18 at 16:36
  • Do you mean keep my same logic but remove the AppDomainSetup and just use the CreateDomain(string)? I did this but keep getting the same exception. – Islam El-gendy May 29 '18 at 21:37
  • You might have to Unload and Remove from AppDomain to unload the assembly. Try example on https://blog.vcillusion.co.in/sending-events-through-application-domain-boundary/ Hope it helps! – vCillusion May 29 '18 at 21:37
  • @vCillusion thanks for the link...helpful article for sure :) – Islam El-gendy May 29 '18 at 22:35

1 Answers1

1

You define a GetAssembly method in your proxy domain, which pulls the loaded Assembly into the main domain. 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.

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:

In an AppDomain you can only load assemblies, which reside in the same or a subfolder of the main domain. Use this instead of Environment.CurrentDirectory in the setup object:

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[]:

var dll = Assembly.Load(File.ReadAllBytes(fullPathToDll));
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • First of all thanks for your reply and I apologize for I'm not getting this completely. About your first part, where do I specify the target dll that I need in your code? and I don't need to send any data, but only call a certain void function, do I still need the sendData(object)? or did you mean that the I should load my dll in the sendData function in the DomainHost class and specify the dll path in the call in DoWork()? In your final part I replaced the AppDomainSetup as you said, but still getting the same exception. – Islam El-gendy May 29 '18 at 21:23
  • I worked it out in your code. I had to copy the dll to my bin folder of my project, but I used it correctly and was able to release it afterwards, which is a big breakthrough for me. I can work things out this way. However, is there a way to load the dll from its location without having to copy it in my bin folder? – Islam El-gendy May 29 '18 at 22:10
  • I'm glad it worked for you. If you don't need any feedbacks the host is not needed of course. Or it can be enough to return a bool or something from the `DoWork`. For the location: see my last line. You can load the dll as byte array, which can be loaded as an `Assembly`. Of course you might need handle dependent assemblies if any. – György Kőszeg May 30 '18 at 05:35
  • using the last line makes me unable to release the dll afterwards. Also, `Environment.CurrentDirectory` and `AppDomain.CurrentDomain.BaseDirectory` gives the same path in the debug. Hence, making the same `FileNotFoundException` as the dll is not in the Application bin folder and again I must copy it there to make it work. – Islam El-gendy May 30 '18 at 14:23
  • "using the last line makes me unable to release the dll afterwards" - you cannot unload the `AppDomain` or what do you mean? – György Kőszeg May 30 '18 at 17:26
  • `Environment.CurrentDirectory` can be changed so it is just a coincidence if it matches. Do not use that for such purpose. – György Kőszeg May 30 '18 at 17:27
  • `AppDomain.Unload(domain);` does not have an effect, the dll can not be used after this `being used by another process` – Islam El-gendy May 30 '18 at 17:48
  • can you assist in this? [link](https://stackoverflow.com/questions/50614823/release-dll-and-pdb-after-loading-dll-file-dynamically) – Islam El-gendy May 31 '18 at 00:13