simple question, probably easy for you to answer.
I have a dll named "MigrationSteps.dll" in the same output folder of my application.
What I want to do, is to load this assembly in a new AppDomain and execute a method on an instance of a class inside this DLL.
Here's my code
string migrationStepsDllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "MigrationSteps.dll");
AppDomainSetup appDomainSetup = new AppDomainSetup() { PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory };
Evidence evidence = AppDomain.CurrentDomain.Evidence;
AppDomain appDomain = AppDomain.CreateDomain("MigrationAppDomain", evidence, appDomainSetup);
//NOT WORKING
Assembly assembly = appDomain.Load(#"C:\Output\Debug\OptimeToolbench\MigrationSteps.dll");
//WORKING
Assembly assembly = Assembly.LoadFrom(#"C:\Output\Debug\OptimeToolbench\MigrationSteps.dll"); ****works.
//This part works well
Type type = assembly.GetType("MigrationSteps.Foo");
object foo = Activator.CreateInstance(type);
MethodInfo methodInfo = type.GetMethod("HelloWorld");
methodInfo.Invoke(foo, null);
AppDomain.Unload(appDomain);
Everytime the line indicated as not working throws a
FileNotFoundException
.
Why's that ?
Thanks for you time.
Add "C:\Output\Debug\OptimeToolbench\" to the PrivateBinPath of the AppDomain. Also do not pass in the file name, pass in the assembly name -- I'm assuming that would be MigrationSteps.
appDomain.Load(string) takes in an assembly name (the strong-name) - NOT the path of where the file is on disk!
Related
I understand loaded assembly can not be unloaded directly so I created temporary domain and loaded assembly but I got error in creating shadow files.
The code I wrote is as follows:
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
[STAThread]
static void Main(string[] args)
{
string startupPath = #"D:\Temp\MyLib\bin\Debug";
string cachePath = Path.Combine(startupPath, "__cache");
string assembly = Path.Combine(startupPath, "MyLib.dll");
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "MyLIb";
setup.ShadowCopyFiles = "true";
setup.CachePath = cachePath;
AppDomain domain = AppDomain.CreateDomain("MyLIb", AppDomain.CurrentDomain.Evidence, setup);
var np = FindFileInPath("MyLib.dll", cachePath);
var ass = Assembly.LoadFile(np);
var types = ass.GetTypes();
AppDomain.Unload(domain);
}
I got error in finding file in path "could not find a part of the path 'D:\Temp\MyLib\bin\Debug__cache'."
public static string FindFileInPath(string filename, string path)
{
filename = filename.ToLower();
foreach (var fullFile in Directory.GetFiles(path))
{
var file = Path.GetFileName(fullFile).ToLower();
if (file == filename)
return fullFile;
}
foreach (var dir in Directory.GetDirectories(path))
{
var file = FindFileInPath(filename, dir);
if (!string.IsNullOrEmpty(file))
return file;
}
return null;
}
}
Could someone help me out of this trouble.
Thanks in advance.
Joon
To start a new appdomain you need to set the ApplicationBase property of your AppDomainSetup. This is the path of the dll's you want to load. Also, you need to set the ShadowCopyDirectories property of your AppDomainSetup object. This will make a shadow copy of all the dll's that are in the given directories.The CachePath determines where the shadowfolders will be created.
For more information see: AppDomainSetup met property ShadowCopyDirectories
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = "MyLIb";
setup.ApplicationBase = dllsDir;
setup.ShadowCopyFiles = "true";
setup.CachePath = cachePath;
setup.ShadowCopyDirectories = dllsDir;
AppDomain = AppDomain.CreateDomain("MyAppDomain", null, setup);
You first need to invoke the CreateInstanceAndUnwrap method of the new appdomain. Then you can begin loading dll's in the new Appdomain.
var proxy = AppDomain.CreateInstanceAndUnwrap(GetType().Assembly.GetName().ToString(), typeof(Proxy).FullName);
var assembly = proxy.GetAssembly(fullPathToDllInDllsDir);
This is the "Proxy" class you can create. Note that the most important part of this class is the MarshalByRefObject
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.Load(AssemblyName.GetAssemblyName(assemblyPath));
}
catch (Exception)
{
return null;
}
}
}
This will create a shadowfolder for that dll and load it in the appdomain (If ShadowCopyFiles is set to true)
Extra: Try to use the Assembly.Load() or Assembly.LoadFrom() methods and try to avoid the use of Assembly.LoadFile(). This is only needed in very specific cases. See this explanation
For assemblies that are loaded without context, the problem can be caused by using the Assembly.LoadFile method to load the same assembly from different paths. The runtime considers two assemblies that are loaded from different paths to be different assemblies, even if their identities are the same.
Note that you can use the Assembly.LoadFrom method to load these assemblies. Because they are now in the probing path, they will be loaded into the default load context instead of the load-from context. However, we recommend that you switch to the Assembly.Load method and supply full assembly display names to ensure that correct versions are always used.
I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)
As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually.
So when I do:
string dir = #"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
and got FileNotFoundException:
Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I think the key part is one of its dependencies.
Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
But got FileNotFoundException again, on another (referenced) assembly.
How to load all references recursively?
Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?
You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:
domain.Load(AssemblyName.GetAssemblyName(path));
Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.
Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).
In simple code:
public void DoStuffInOtherDomain()
{
const string assemblyPath = #"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.
For example, the app domain creation line from the above code should be replaced with:
var dllsSearchPath = #"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
This way, all the dlls will automaically be resolved from dllsSearchPath.
http://support.microsoft.com/kb/837908/en-us
C# version:
Create a moderator class and inherit it from MarshalByRefObject:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
call from client site
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.
You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
It took me a while to understand #user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = #"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
The Key is the AssemblyResolve event raised by the AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(#"c:\Provisioning\") == false)
Directory.CreateDirectory(#"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(#"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
I have had to do this several times and have researched many different solutions.
The solution I find in most elegant and easy to accomplish can be implemented as such.
1. Create a project that you can create a simple interface
the interface will contain signatures of any members you wish to call.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.
2. Now create project that has the code you want to load in seperate AppDomain.
This project as with the client proj will reference the proxy proj and you will implement the interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
3. Next, in the client project, load code in another AppDomain.
So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only assembly type reference
var #type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( #type.Assembly.Name, #type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.
There you have it.
This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.
To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.
The key is to either make sure you code either derives MarshalByRefObject or is serializable.
`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.
I hope this helps.
if i use
Assembly assembly = Assembly.LoadFrom(file);
and later try to use the file , i get an exception stating that the file is in use .
i need to load it on to a new appdomain .
all i seem to find is examples of how to create an instance with in the Assembly ,
is there a way to load the entire assembly.
what i need is to :
(1) load the assembly into a new AppDomain from a file .
(2) extract an embedded resource (xml file) from the Dll .
(3) extract a type of class which implements an interface (which i know the interface type) .
(4) unload the entire appdomain in order to free the file .
2-4 is not a problem
i just can't seem to find how to load the Assembly into a new AppDomin , only examples of
create instance , which gives me an instace of the class from with in the Dll .
i need the entire thing.
like in this question : another example of Create instance .
Loading DLLs into a separate AppDomain
The most basic multidomain scenario is
static void Main()
{
AppDomain newDomain = AppDomain.CreateDomain("New Domain");
newDomain.ExecuteAssembly("file.exe");
AppDomain.Unload(newDomain);
}
Calling ExecuteAssembly on a seperate domain is convienient but does not offer the ability to interact with the domain itself. It also requires the target assembly to be an executable and forces the caller to a single entry point. To incorporate some flexibility you could also pass a string or args to the .exe.
I hope this helps.
Extension: Try something like the following then
AppDomainSetup setup = new AppDomainSetup();
setup.AppDomainInitializer = new AppDomainInitializer(ConfigureAppDomain);
setup.AppDomainInitializerArguments = new string[] { unknownAppPath };
AppDomain testDomain = AppDomain.CreateDomain("test", AppDomain.CurrentDomain.Evidence, setup);
AppDomain.Unload(testDomain);
File.Delete(unknownAppPath);
where the AppDomain can be initilised as follows
public static void ConfigureAppDomain(string[] args)
{
string unknownAppPath = args[0];
AppDomain.CurrentDomain.DoCallBack(delegate()
{
//check that the new assembly is signed with the same public key
Assembly unknownAsm = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(unknownAppPath));
//get the new assembly public key
byte[] unknownKeyBytes = unknownAsm.GetName().GetPublicKey();
string unknownKeyStr = BitConverter.ToString(unknownKeyBytes);
//get the current public key
Assembly asm = Assembly.GetExecutingAssembly();
AssemblyName aname = asm.GetName();
byte[] pubKey = aname.GetPublicKey();
string hexKeyStr = BitConverter.ToString(pubKey);
if (hexKeyStr == unknownKeyStr)
{
//keys match so execute a method
Type classType = unknownAsm.GetType("namespace.classname");
classType.InvokeMember("MethodNameToInvoke", BindingFlags.InvokeMethod, null, null, null);
}
});
}
I am writing a plugin architecture. My plugin dlls are located in a sub directory from where the plugin manager is running.
I am loading the plugins into a separate AppDomain as the following:
string subDir;//initialized to the path of the module's directory.
AppDomainSetup setup = new AppDomainSetup();
setup.PrivateBinPath = subDir;
setup.ApplicationBase = subDir;
AppDomain newDomain= AppDomain.CreateDomain(subDir, null, setup);
byte[] file = File.ReadAllBytes(dllPath);//dll path is a dll inside subDir
newDomain.Load(file);
However. newDomain.Load returns an assembly which the currently domain attempts to load. Because the plugin dlls are in a sub directory, the current domain cannot and should not see these dlls and the current domain throws a FileLoadException
"ex = {"Could not load file or assembly ... or one of its dependencies."
The question is, can we load an assembly into a separate AppDomain without it returning the loaded assembly?
I know I can add a handler for the AssemblyResolve event in the current domain and return a null, but I would prefer to not to go this route.
Thanks in advance.
You can also use DoCallBack - here's something I put together after reading lots about it on SO. This creates an appdomain, checks that the assemblies have the same signature public key, loads the assembly, executes a static method, unloads the appdomain, then deletes the dll.
static void Main(string[] args)
{
string unknownAppPath = #"path-to-your-dll";
Console.WriteLine("Testing");
try
{
AppDomainSetup setup = new AppDomainSetup();
setup.AppDomainInitializer = new AppDomainInitializer(TestAppDomain);
setup.AppDomainInitializerArguments = new string[] { unknownAppPath };
AppDomain testDomain = AppDomain.CreateDomain("test", AppDomain.CurrentDomain.Evidence, setup);
AppDomain.Unload(testDomain);
File.Delete(unknownAppPath);
}
catch (Exception x)
{
Console.WriteLine(x.Message);
}
Console.ReadKey();
}
public static void TestAppDomain(string[] args)
{
string unknownAppPath = args[0];
AppDomain.CurrentDomain.DoCallBack(delegate()
{
//check that the new assembly is signed with the same public key
Assembly unknownAsm = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(unknownAppPath));
//get the new assembly public key
byte[] unknownKeyBytes = unknownAsm.GetName().GetPublicKey();
string unknownKeyStr = BitConverter.ToString(unknownKeyBytes);
//get the current public key
Assembly asm = Assembly.GetExecutingAssembly();
AssemblyName aname = asm.GetName();
byte[] pubKey = aname.GetPublicKey();
string hexKeyStr = BitConverter.ToString(pubKey);
if (hexKeyStr == unknownKeyStr)
{
//keys match so execute a method
Type classType = unknownAsm.GetType("namespace.classname");
classType.InvokeMember("method-you-want-to-invoke", BindingFlags.InvokeMethod, null, null, null);
}
});
}
As pointed out in links given below:
Loading/Unloading assembly in different AppDomain
Load Assembly in New AppDomain without loading it in Parent AppDomain
It seems that calling Load() method on another AppDomain object causes that assembly to be loaded in current AppDomain as well.
The solution is to use CreateInstanceFromAndUnwrap() method of AppDomain class instead. You pass a path to assembly and a type to be converted to.
I want to load to a new AppDomain some assembly which has a complex references tree (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll)
As far as I understood, when an assembly is being loaded to AppDomain, its references would not be loaded automatically, and I have to load them manually.
So when I do:
string dir = #"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory
string path = System.IO.Path.Combine(dir, "MyDll.dll");
AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
setup.ApplicationBase = dir;
AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup);
domain.Load(AssemblyName.GetAssemblyName(path));
and got FileNotFoundException:
Could not load file or assembly 'MyDll, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified.
I think the key part is one of its dependencies.
Ok, I do next before domain.Load(AssemblyName.GetAssemblyName(path));
foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies())
{
domain.Load(refAsmName);
}
But got FileNotFoundException again, on another (referenced) assembly.
How to load all references recursively?
Do I have to create references tree before loading root assembly? How to get an assembly's references without loading it?
You need to invoke CreateInstanceAndUnwrap before your proxy object will execute in the foreign application domain.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
var assembly = value.GetAssembly(args[0]);
// AppDomain.Unload(domain);
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
Also, note that if you use LoadFrom you'll likely get a FileNotFound exception because the Assembly resolver will attempt to find the assembly you're loading in the GAC or the current application's bin folder. Use LoadFile to load an arbitrary assembly file instead--but note that if you do this you'll need to load any dependencies yourself.
Once you pass the assembly instance back to the caller domain, the caller domain will try to load it! This is why you get the exception. This happens in your last line of code:
domain.Load(AssemblyName.GetAssemblyName(path));
Thus, whatever you want to do with the assembly, should be done in a proxy class - a class which inherit MarshalByRefObject.
Take in count that the caller domain and the new created domain should both have access to the proxy class assembly. If your issue is not too complicated, consider leaving the ApplicationBase folder unchanged, so it will be same as the caller domain folder (the new domain will only load Assemblies it needs).
In simple code:
public void DoStuffInOtherDomain()
{
const string assemblyPath = #"[AsmPath]";
var newDomain = AppDomain.CreateDomain("newDomain");
var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName);
asmLoaderProxy.GetAssembly(assemblyPath);
}
class ProxyDomain : MarshalByRefObject
{
public void GetAssembly(string AssemblyPath)
{
try
{
Assembly.LoadFrom(AssemblyPath);
//If you want to do anything further to that assembly, you need to do it here.
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message, ex);
}
}
}
If you do need to load the assemblies from a folder which is different than you current app domain folder, create the new app domain with specific dlls search path folder.
For example, the app domain creation line from the above code should be replaced with:
var dllsSearchPath = #"[dlls search path for new app domain]";
AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true);
This way, all the dlls will automaically be resolved from dllsSearchPath.
http://support.microsoft.com/kb/837908/en-us
C# version:
Create a moderator class and inherit it from MarshalByRefObject:
class ProxyDomain : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFrom(assemblyPath);
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
}
call from client site
ProxyDomain pd = new ProxyDomain();
Assembly assembly = pd.GetAssembly(assemblyFilePath);
On your new AppDomain, try setting an AssemblyResolve event handler. That event gets called when a dependency is missing.
You need to handle the AppDomain.AssemblyResolve or AppDomain.ReflectionOnlyAssemblyResolve events (depending on which load you're doing) in case the referenced assembly is not in the GAC or on the CLR's probing path.
AppDomain.AssemblyResolve
AppDomain.ReflectionOnlyAssemblyResolve
It took me a while to understand #user1996230's answer so I decided to provide a more explicit example. In the below example I make a proxy for an object loaded in another AppDomain and call a method on that object from another domain.
class ProxyObject : MarshalByRefObject
{
private Type _type;
private Object _object;
public void InstantiateObject(string AssemblyPath, string typeName, object[] args)
{
assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory
_type = assembly.GetType(typeName);
_object = Activator.CreateInstance(_type, args); ;
}
public void InvokeMethod(string methodName, object[] args)
{
var methodinfo = _type.GetMethod(methodName);
methodinfo.Invoke(_object, args);
}
}
static void Main(string[] args)
{
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = #"SomePathWithDLLs";
AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup);
ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject");
proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs});
proxyObject.InvokeMethod("foo",new object[] { "bar"});
}
The Key is the AssemblyResolve event raised by the AppDomain.
[STAThread]
static void Main(string[] args)
{
fileDialog.ShowDialog();
string fileName = fileDialog.FileName;
if (string.IsNullOrEmpty(fileName) == false)
{
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
if (Directory.Exists(#"c:\Provisioning\") == false)
Directory.CreateDirectory(#"c:\Provisioning\");
assemblyDirectory = Path.GetDirectoryName(fileName);
Assembly loadedAssembly = Assembly.LoadFile(fileName);
List<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>();
foreach (var type in assemblyTypes)
{
if (type.IsInterface == false)
{
StreamWriter jsonFile = File.CreateText(string.Format(#"c:\Provisioning\{0}.json", type.Name));
JavaScriptSerializer serializer = new JavaScriptSerializer();
jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type)));
jsonFile.Close();
}
}
}
}
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string[] tokens = args.Name.Split(",".ToCharArray());
System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name);
return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"}));
}
I have had to do this several times and have researched many different solutions.
The solution I find in most elegant and easy to accomplish can be implemented as such.
1. Create a project that you can create a simple interface
the interface will contain signatures of any members you wish to call.
public interface IExampleProxy
{
string HelloWorld( string name );
}
Its important to keep this project clean and lite. It is a project that both AppDomain's can reference and will allow us to not reference the Assembly we wish to load in seprate domain from our client assembly.
2. Now create project that has the code you want to load in seperate AppDomain.
This project as with the client proj will reference the proxy proj and you will implement the interface.
public interface Example : MarshalByRefObject, IExampleProxy
{
public string HelloWorld( string name )
{
return $"Hello '{ name }'";
}
}
3. Next, in the client project, load code in another AppDomain.
So, now we create a new AppDomain. Can specify the base location for assembly references. Probing will check for dependent assemblies in GAC and in current directory and the AppDomain base loc.
// set up domain and create
AppDomainSetup domaininfo = new AppDomainSetup
{
ApplicationBase = System.Environment.CurrentDirectory
};
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo);
// assembly ant data names
var assemblyName = "<AssemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|<keyIfSigned>";
var exampleTypeName = "Example";
// Optional - get a reflection only assembly type reference
var #type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName );
// create a instance of the `Example` and assign to proxy type variable
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName );
// Optional - if you got a type ref
IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( #type.Assembly.Name, #type.Name );
// call any members you wish
var stringFromOtherAd = proxy.HelloWorld( "Tommy" );
// unload the `AppDomain`
AppDomain.Unload( exampleDomain );
if you need to, there are a ton of different ways to load an assembly. You can use a different way with this solution. If you have the assembly qualified name then I like to use the CreateInstanceAndUnwrap since it loads the assembly bytes and then instantiates your type for you and returns an object that you can simple cast to your proxy type or if you not that into strongly-typed code you could use the dynamic language runtime and assign the returned object to a dynamic typed variable then just call members on that directly.
There you have it.
This allows to load an assembly that your client proj doesnt have reference to in a seperate AppDomain and call members on it from client.
To test, I like to use the Modules window in Visual Studio. It will show you your client assembly domain and what all modules are loaded in that domain as well your new app domain and what assemblies or modules are loaded in that domain.
The key is to either make sure you code either derives MarshalByRefObject or is serializable.
`MarshalByRefObject will allow you to configure the lifetime of the domain its in. Example, say you want the domain to destroy if the proxy hasnt been called in 20 minutes.
I hope this helps.