2
votes

I'm trying to load a DLL file into a separated app domain and invoke a method in the DLL file and get some response from it. The DLL file did not exist in the project bin folder when the application starts, the DLL file is loaded from another folder. After I have done with the DLL file I want to unload the app domain that I have just created.

The steps:

  1. Created a new app domain
  2. Load my DLL I want to the app domain
  3. Invoke the method and get response
  4. Unload the app domain

Here is what I've tried so far

This is the code in MyAssembly.dll

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MyAssembly
{
    public class MyClass
    {
        public static string MyMethod()
        {
            return "Hello there, this is message from MyAssembly";
        }
    }
}

Here is how I load the DLL file

using System.Diagnostic;
using System.IO;

private class ProxyClass : MarshalByRefObject
{
    public void LoadAssembly()
    {
        AppDomain dom;
        string domainName = "new:" + Guid.NewGuid();
        try
        {
            //Create the app domain
            dom = AppDomain.CreateDomain(domainName, null, new AppDomainSetup
                    {
                        PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"),
                        ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                        ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                        ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
                        ShadowCopyFiles = "true",
                        ShadowCopyDirectories = "true",
                        LoaderOptimization = LoaderOptimization.SingleDomain,
                    });

            string dllPath = @"C:\MyProject\MyAssembly.dll";//the path to my assembly file I want to load
            //load the assembly to the new app domain
            Assembly asm = dom.Load(File.ReadAllBytes(dllPath));//Error occurred at here

            Type baseClass = asm.GetType("MyAssembly.MyClass");
            MethodInfo targetMethod = baseClass.GetMethod("MyMethod");

            string result = targetMethod.Invoke(null, new object[]{});

            /*Do something to the result*/
        }
        catch(Exception ex)
        {
            Debug.WriteLine(ex.Message);
            Debug.WriteLine(ex.ToString());
        }
        finally
        {
            //Finally unload the app domain
            if (dom != null) AppDomain.Unload(dom);
        }
    }
}

public void BeginLoadDll()
    {
        ProxyClass proxy = new ProxyClass();
        proxy.LoadAssembly();

        //OR like this, which gave me same error message as well
        //var dom = AppDomain.CreateDomain("new:" + Guid.NewGuid(), null, new AppDomainSetup
        //    {
        //        PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"),
        //        ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
        //        ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
        //        ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
        //        ShadowCopyFiles = "true",
        //        ShadowCopyDirectories = "true",
        //        LoaderOptimization = LoaderOptimization.SingleDomain,
        //    });
        //ProxyClass proxy = (ProxyClass)dom.CreateInstanceAndUnwrap(
        //    typeof(ProxyClass).Assembly.FullName, typeof(ProxyClass).FullName);
        //pr.LoadAssembly(watcherData, filePath);
    }

Here is something I've observed so far, I'm not sure if that is just me or I'm missing something

-If the "MyAssembly.dll" exists in the project bin folder before the application starts, I can load the dll file

-If the "MyAssembly.dll" did not exist in the project bin folder before application starts, instead it was loaded somewhere else other than the project bin folder, I cannot load the dll file. For example, the project bin folder is "C:\Main\MyMainProject\MyMainProject\bin", and DLL is loaded from C:\MyProject\MyAssembly.dll"

-If I move the "MyAssembly.dll" file into the bin folder (using File.Copy() or File.Move()), it somehow stop the rest of the code to be executed.

The error message I received

Could not load file or assembly 'MyAssembly, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=2c20c56a5e1f4bd4' or one of its dependencies.
The system cannot find the file specified.

EDIT

I know I can use Assembly.LoadFrom(@"PATH\TO\MY\DLL"), but the problem with this one is I cannot unload the DLL

1
you need to load the dependencies for the dll you are trying to load first.Seabizkit
The code in the snippet is not consistent with the behavior you describe. 2) is normal for regular assembly loads, if it failed once then it will consistently fail, even though it might be possible after copying a file. But not with Load(byte[]), it never saw the load at all so can't fail it again. 3) is completely abnormal, you can't restart a program by just copying a file. So there is some other code that actually loads assemblies, you might find it if you search for FileSystemWatcher.Hans Passant

1 Answers

1
votes

After days of research, I finally got it working. Below is my final working code.

Useful reference links that helped me achieved this

https://docs.microsoft.com/en-us/dotnet/api/system.appdomain.createinstanceandunwrap?view=netframework-4.8#System_AppDomain_CreateInstanceAndUnwrap_System_String_System_String_

C# reflection - load assembly and invoke a method if it exists

Using AppDomain in C# to dynamically load and unload dll

The code in MyAssembly.dll is same as in the question. I also realized that I can return an object type as well.

How I load the DLL file into separated app domain and unload the app domain

public void MethodThatLoadDll()
{
    AppDomain dom = null;
    //declare this outside the try-catch block, so we can unload it in finally block

    try
    {
        string domName = "new:" + Guid.NewGuid();
        //assume that the domName is "new:50536e71-51ad-4bad-9bf8-67c54382bb46"

        //create the new domain here instead of in the proxy class
        dom = AppDomain.CreateDomain(, null, new AppDomainSetup
                    {
                        PrivateBinPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"),
                        ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
                        ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile,
                        ApplicationName = AppDomain.CurrentDomain.SetupInformation.ApplicationName,
                        ShadowCopyFiles = "true",
                        ShadowCopyDirectories = "true",/*yes they are string value*/
                        LoaderOptimization = LoaderOptimization.SingleDomain,
                        DisallowBindingRedirects = false,
                        DisallowCodeDownload = true,
                    });
        ProxyClass proxy = (ProxyClass)dom.CreateInstanceAndUnwrap(
                    typeof(ProxyClass).Assembly.FullName, typeof(ProxyClass).FullName);
        string result = proxy.ExecuteAssembly("MyParam");
        /*Do whatever to the result*/
    }
    catch(Exception ex)
    {
        //handle the error here
    }
    finally
    {
        //finally unload the app domain
        if(dom != null) AppDomain.Unload(dom);
    }

}

My class that inherits MarshalByRefObject

private class ProxyClass : MarshalByRefObject
{
    //you may specified any parameter you want, if you get `xxx is not marked as serializable` error, see explanation below
    public string ExecuteAssembly(string param1)
    {
        /*
         * All the code executed here is under the new app domain that we just created above
         * We also have different session state here, so if you want data from main domain's session, you should pass it as a parameter
         */
        //load your DLL file here
        Debug.WriteLine(AppDomain.CurrentDomain.FriendlyName);
        //will print "new:50536e71-51ad-4bad-9bf8-67c54382bb46" which is the name that we just gave to the new created app domain

        Assembly asm = Assembly.LoadFrom(@"PATH/TO/THE/DLL");

        Type baseClass = asm.GetType("MyAssembly.MyClass");
        MethodInfo targetMethod = baseClass.GetMethod("MyMethod");

        string result = targetMethod.Invoke(null, new object[]{});

        return result;
    }
}

A common error that you may run into

'xxx' is not marked as serializable

This could happen if you try to pass a custom class as parameter, like this

public void ExecuteAssembly(MyClass param1)

In this case, put a [Serializable] to MyClass, like this

[Serializable]
public class MyClass { }