Loading Dlls into a Separate Appdomain

Loading DLLs into a separate AppDomain

More specifically

AppDomain domain = AppDomain.CreateDomain("New domain name");
//Do other things to the domain like set the security policy

string pathToDll = @"C:\myDll.dll"; //Full path to dll you want to load
Type t = typeof(TypeIWantToLoad);
TypeIWantToLoad myObject = (TypeIWantToLoad)domain.CreateInstanceFromAndUnwrap(pathToDll, t.FullName);

If all that goes properly (no exceptions thrown) you now have an instance of TypeIWantToLoad loaded into your new domain. The instance you have is actually a proxy (since the actual object is in the new domain) but you can use it just like your normal object.

Note: As far as I know TypeIWantToLoad has to inherit from MarshalByRefObject.

Loading .dll into separate application domain (MEF)

MEF is more about extensibility and it seems like you actually looking for [MAF]1. Check out this SO question. From my personal experience if your plugins going to invoke some native code (interact with hardware for example) AppDomains will not prevent crashes. It's safer to host such plugins in separate processes.

Loading DLL dynamically into separated app domain then unload

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

Useful reference links that helped me achieved this

https://learn.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 { }

Load a DLL into a different AppDomain and his dependencies

Despite I have not applied directly the suggestions of @James Barras, his comments help me to find this solutions. So, thanks for take the time to help me :)

This is a posible solution to anyone in my situation:

  1. The class you want to deserialize must inheriht from MarshalByRefObject
  2. The objects returned by the methods inside your class must be serializable
  3. Create this class:

    Imports System.Reflection

    Public Class Loader
    Inherits MarshalByRefObject
    Private Function CallInternal(dll As String, typename As String, method As String, parameters As Object()) As Object
    Dim a As Assembly = Assembly.LoadFile(dll)
    Dim o As Object = a.CreateInstance(typename)
    Dim t As Type = o.[GetType]()
    Dim m As MethodInfo = t.GetMethod(method)
    Return m.Invoke(o, parameters)
    End Function
    Public Shared Function [Call](dll As String, typename As String, method As String, ParamArray parameters As Object()) As Object
    Dim dom As AppDomain = AppDomain.CreateDomain("MyNewDomain")
    Dim ld As Loader = DirectCast(dom.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, GetType(Loader).FullName), Loader)
    Dim result As Object = ld.CallInternal(dll, typename, method, parameters)
    AppDomain.Unload(dom)
    Return result
    End Function
    End Class
  4. Use this code to call a method inside the dll you want to load:

    Loader.Call(pathToDLL, ClasName,MethodName, parameters)

This solution unload the domain after you call any method. So it is not perfect, because if you want to call several methods, you will be penalized on execution time.

Using AppDomain in C# to dynamically load and unload dll

Thanks guys, here is link where i found answer to my quetion:

The MSDN forum description for load and unload of assemblies dynamically

The other dll can be dynamically loaded and unloaded using another class which does load assembly and and call methods in that assembly...
AppDomain.CreateInstanceAndUnwrap generally wants input as assemblies from current project or generally current namespace. to solve that i need Assembly.LoadFrom(); to be used in some other class and create AppDomain and create instance of this class using AppDomain object as given in link.

Thanks for ur replies guys.

Load and unload a dll dynamically into my project using AppDomain

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

Load different version of assembly into separate AppDomain

I think your problem is the following:

Assembly asm = pluginDomain.GetAssemblies().First(a => a.GetName().Name == "Plugin");
Type p = asm.GetTypes().First(t => t.GetInterfaces().Contains(typeof(IPlugin)));
var pluginInstance = (IPlugin)pluginDomain.CreateInstanceAndUnwrap(asm.FullName, p.FullName);

With this you're loading the updated/outdated type references into the main AppDomain (which has the assembly already loaded in a different version).

I recommend you use the following approach:

  1. Create a separate assembly that contains the contracts/Interfaces. This Assembly is fixed and remains at a specific version all the time and it can never be outdated and each version of your plugin can reference it.
  2. Write an assembly loader that is part of your host application, but in a seperate assembly as well. This assembly loader assembly must not have any reference to the rest of your application so you can set it up in the new appdomain.
  3. After setting up the new appdomain, load an instance of your AssemblyLoader. This one loads the plugin assemblies and interacts with it.
  4. Your application doesn't interact with the assembly itself. Your application calls your AssemblyLoader via the appdomain boundary and the AssemblyLoader calls the Plugin

I can't say if this is the best version possible, but this one works pretty well for me in an environment where each plugin can load any version of any assembly. With this setup you're pretty much resistant to changing versions or updates and each plugin can use the version it wants.

Let me know if it works for you.

C# Loading a DLL byte array into a different AppDomain throws System.IO.FileNotFoundException

SOLUTION:

AppDomains are poorly documented and poorly explained no matter where I look. It's like people are trying to hide it and keep it a secret from the public mass. Apparently AppDomains do not share data between each other like variables and other object references. You need to SetData/GetData and DoCallBack between them. This was vaguely mentioned but no real solution was given by anyone.

So I did this simple plugin loader, using "LoadFrom" without loading it into a byte array and the file does not get locked, it reads it into a new AppDomain into memory and unlocks the file immediately but this is not mentioned anywhere and is weird behavior already because in the main AppDomain it locks onto the file like a cancer.

[Serializable] //This is important
public class TPlugin
{
public bool InitializeImmediately { get; set; }
public AppDomainSetup AppSetup { get; set; }
public Assembly Assembly { get; set; }
public AppDomain AppDomain { get; set; }
public string FilePath { get; set; }
public object ClassInstance { get; set; }
public Type ClassType { get; set; }

public TPlugin(string path, bool Initialize = false)
{
FilePath = path;
InitializeImmediately = Initialize;

AppSetup = new AppDomainSetup();
AppSetup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
AppDomain = AppDomain.CreateDomain(FilePath, null, AppSetup);
AppDomain.SetData("Plugin", this);
AppDomain.DoCallBack(new CrossAppDomainDelegate(() =>
{
//We are now inside the new AppDomain, every other variable is now invalid since this AppDomain cannot see into the main one
TPlugin plugin = AppDomain.CurrentDomain.GetData("Plugin") as TPlugin;
if (plugin != null)
{
plugin.Assembly = Assembly.LoadFrom(plugin.FilePath);
if(InitializeImmediately) //You cannot use the "Initialize" parameter here, it goes out of scope for this AppDomain
{
plugin.ClassType = plugin.Assembly.GetExportedTypes()[0];
if (plugin.ClassType != null && plugin.ClassType.IsClass)
{
plugin.ClassInstance = Activator.CreateInstance(plugin.ClassType);
MethodInfo info = plugin.ClassType.GetMethod("Initializer");
info.Invoke(plugin.ClassInstance, null);
}
}
}
}));
}

public object Execute(string FunctionName, params object[] args)
{
AppDomain.SetData("FunctionName", FunctionName);
AppDomain.SetData("FunctionArguments", args);
AppDomain.DoCallBack(CallBack);
return AppDomain.GetData("FunctionReturn");
}

public void CallBack()
{
TPlugin plugin = AppDomain.CurrentDomain.GetData("Plugin") as TPlugin;

if (plugin != null)
{
MethodInfo info = plugin.ClassType.GetMethod(AppDomain.CurrentDomain.GetData("FunctionName") as string);
info.Invoke(plugin.ClassInstance, AppDomain.CurrentDomain.GetData("FunctionArgs") as object[]);
}

//This is how to return back since DoCallBack does not support returns.
AppDomain.CurrentDomain.SetData("FunctionReturn", null);
}
}

And this is my DLL module:

public class GUIModule
{
public bool Initializer()
{
Console.WriteLine("Initialized!");
return true;
}

public bool Deinitializer()
{
Console.WriteLine("Deinitialized");
return true;
}
}

All works fine now, even loads the dependencies. GUIModule has a reference to Windows.Forms when compiled.



Related Topics



Leave a reply



Submit