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:
- The class you want to deserialize must inheriht from MarshalByRefObject
- The objects returned by the methods inside your class must be serializable
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 ClassUse 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:
- 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.
- 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.
- After setting up the new appdomain, load an instance of your AssemblyLoader. This one loads the plugin assemblies and interacts with it.
- 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
An Attribute Argument Must Be a Constant Expression, ...- Create an Attribute of Type Array
Main Method Code Entirely Inside Try/Catch: Is It Bad Practice
Webclient.Downloadstring() Returns String with Peculiar Characters
How to Unregister 'Anonymous' Event Handler
Multiple String Comparison with C#
C# Rsa Public Key Output Not Correct
How to Implement Url Rewriting Similar to So
How to Make My Product as a Trial Version for 30 Days
Selenium Error - the Http Request to the Remote Webdriver Timed Out After 60 Seconds
Why Aren't C# Static Class Extension Methods Supported
Best Way to Determine If Two Path Reference to Same File in C#
Loading Dlls into a Separate Appdomain
How to Put a Task to Sleep (Or Delay) in C# 4.0
Is There Really Any Way to Uniquely Identify Any Computer at All
C# - Winforms - Global Variables
Compare Two Datatables to Determine Rows in One But Not the Other
Pair-Wise Iteration in C# or Sliding Window Enumerator
How Does Wcf Deserialization Instantiate Objects Without Calling a Constructor