ShadowCopy assemblies - c#

I have a problem and I really have no clue why it doesn't work. I've read a lot of tutorials (also on stackoverflow) and still nothing.
My goal is to use reflection on some .dll files (they are not use by any program yet) and get inheritance types, methods, constructors etc. Everything works correctly but the problem is that dlls are locked and cannot be deleted until I turn off the program. This is part of my code where I try to resolve the problem.
var apds = new AppDomainSetup();
apds.ApplicationName = "MyAssemblies";
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain apd = AppDomain.CreateDomain("newdomain", adevidence, apds);
apd.AppendPrivatePath("Assemblies");
apd.SetCachePath("C:\\Cache");
apd.SetShadowCopyFiles();
foreach (var type in apd.Load(AssemblyName.GetAssemblyName(file.Path)).GetTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
{ //rest is ok
I know I use some deprecated methods but it's one of the try. Dlls are succesfully copied into cache directory but they seems to be loaded into current domain too.
Where's the problem?
Thanks in advance.
I modified my code to and use Loader class but i still have locked files.
class Loader : MarshalByRefObject
{
public Assembly assembly;
public void LoadAssembly(string path)
{
assembly = Assembly.Load(AssemblyName.GetAssemblyName(path));
}
public Types[] getTypes()
{
return assembly.getTypes();
}
}
//...
if (file.Type == ".dll")
{
var apds = new AppDomainSetup
{
ApplicationName = "MyAssemblies",
ApplicationBase = Path,
ShadowCopyFiles = "true",
ShadowCopyDirectories = Path
};
AppDomain apd = AppDomain.CreateDomain("newdomain", null, apds);
Loader loader = (Loader)apd.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader.LoadAssembly(file.Path);
foreach (var type in loader.getTypes())
{
foreach (var inherits in GetInheritanceHierarchy(type))
//...
Any idea?

You can load your assembly in the following way.
var types = Assembly.Load(File.ReadAllBytes("YourAssembly.dll")).GetTypes();
Now you can extract the types from the assembly, and you can delete the assembly while the application is still running.

Related

AppDomain doesn't make shadow files

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.

Load Assembly into a new app domain but not CurrentDomain

So my problem revolves around saving memory.
In essence I need to load an assembly in to a separate app domain other than the main/current domain, check for types within that assembly, and then unload the new domain when done.
Currently my solution is as follows:
AppDomain NewDomain = AppDomain.CreateDomain("newdomain");
foreach(string path in dllPaths) //string list of dll paths
{
byte[] dllBytes = File.ReadAllBytes(dll);
NewDomain.Load(dllBytes); //offending line
}
DoStuffWithNewDomain();
AppDomain.Unload(NewDomain);
The NewDomain.Load line seems to load the assembly into the new domain but also into the current domain of my program.
I used this link as a reference - http://www.csharp411.com/how-to-load-a-net-assembly-into-a-separate-appdomain-so-you-can-unload-it/
Many thanks :)
As mentioned already, loading the assembly from bytes can only happen for the current app domain.
Here is one way to context-switch to make your target app domain the current one, using the DoCallBack method on the app domain (http://msdn.microsoft.com/en-us/library/system.appdomain.docallback%28v=vs.110%29.aspx).
After the assemblies are loaded you can then use the same method to inspect the assemblies and types of the new app domain, and create instances as necessary, before unloading it.
class Program
{
static void Main(string[] args)
{
AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
List<string> dllPaths = new List<string>() { #"c:\dev\taglib-sharp.dll" };
foreach (string dll in dllPaths)
{
AppDomainAsmLoader asmLoad = new AppDomainAsmLoader(File.ReadAllBytes(dll));
newDomain.DoCallBack(new CrossAppDomainDelegate(asmLoad.LoadAsm));
}
newDomain.DoCallBack(new CrossAppDomainDelegate(DoWorkWithAppDomain));
AppDomain.Unload(newDomain);
Console.ReadKey();
}
public static void DoWorkWithAppDomain()
{
Assembly[] asms = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly asm in asms)
{
Type[] types = asm.GetTypes();
foreach (Type type in types)
{
Console.WriteLine("Found the type: {0}", type.FullName);
}
}
}
[Serializable]
public class AppDomainAsmLoader
{
private byte[] AsmData;
public AppDomainAsmLoader(byte[] data)
{
AsmData = data;
}
public void LoadAsm()
{
Assembly asm = Assembly.Load(AsmData);
}
}
}
As Erik suggested, you need to use CreateInstanceAndWrap. Here's an example I did in VB a while back...
Private Function GetCoupler() As IBatchCoupler
Dim CouplerProxy As IBatchCoupler = Nothing
Try
Dim DomainSetupInfo As AppDomainSetup = New AppDomainSetup()
DomainSetupInfo.ConfigurationFile = Path.Combine(mFilePath, "web.config")
DomainSetupInfo.ApplicationBase = Path.Combine(mFilePath, "bin")
DomainSetupInfo.ShadowCopyFiles = "true"
Dim domain As AppDomain = AppDomain.CreateDomain("CoolDomain", AppDomain.CurrentDomain.Evidence, DomainSetupInfo)
'Create remote object in new appDomain via the coupler interface
'to avoid loading the design library into the calling application's primary appDomain
CouplerProxy = domain.CreateInstanceFromAndUnwrap(Path.Combine(DomainSetupInfo.ApplicationBase, "Design.dll"), "BigApplication.Design.BatchCoupler")
Catch ex As Exception
ThisLogger.Error(ex)
End Try
Return CouplerProxy
End Function

Cannot load assembly into application domain: "Could not load file or assembly XXX, Version=1.0.0.0 ..." [duplicate]

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.

C# fully trusted assembly with SecuritySafeCritical funciton still throwing SecurityExceptions

I'm trying to create a sandboxed AppDomain for loading extensions/plugins. I have a MarshalByRefObject that in instantiate inside the appdomain to load the dll. I'm getting SecurityExceptions when trying to load the dll and I can't figure out how to get around them while still limiting what the third party code can do. All my projects are .net 4.
The InDomainLoader class is in a fully trusted domain, the method is marked SecuritySafeCritical. From everything I've read, I think this should work.
Here is my Loader class that creates the AppDomain and jumps into it:
public class Loader
{
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
// Create new AppDomain
var setup = AppDomain.CurrentDomain.SetupInformation;
var permissions = new PermissionSet(null);
permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
var strongname = typeof(InDomainLoader).Assembly.Evidence.GetHostEvidence<StrongName>();
var strongname2 = typeof(IPlugin).Assembly.Evidence.GetHostEvidence<StrongName>();
AppDomain domain = AppDomain.CreateDomain("plugin", null, setup, permissions, strongname, strongname2);
// Create instance
var loader = (InDomainLoader)domain.CreateInstanceAndUnwrap(
typeof (InDomainLoader).Assembly.FullName, typeof (InDomainLoader).FullName);
// Jump into domain
loader.Load(dll, typeName);
}
}
And here's the bootstrap loader that runs in the domain:
public class InDomainLoader : MarshalByRefObject
{
[SecuritySafeCritical]
public void Load(string dll, string typeName)
{
Log.PrintSecurity();
var assembly = Assembly.LoadFrom(dll); // <!-- SecurityException!
var pluginType = assembly.GetType(typeName);
var demoRepository = new DemoRepository();
var plugin = (IPlugin)Activator.CreateInstance(pluginType, demoRepository);
Console.WriteLine(plugin.Run());
}
}
Some logging statements tell me that the assembly's IsFullyTrusted is true and the method has both IsSecurityCritical and IsSecuritySafeCritical set to true, IsSecurityTransparent is false.
I zipped up the whole project to http://davidhogue.com/files/PluginLoader.zip in case that makes this easier.
If anyone has any ideas, I'd be very grateful. I seem to be stuck at a dead end here.
Well for a start you probably shouldn't be marking the function as SecuritySafeCritical as that implies untrusted callers can call you, which you probably don't really want (not that it should be a major issue).
As for your problem the issue is that by default you still don't run with any special permissions, the normal easy way to do the assembly loading is you create your own AppDomainSetup and point it's ApplicationBase at a Plugin directory of some kind (which isn't a bad idea in general), you can then use the normal Assembly.Load("AssemblyName") to load out of the base. However if you must load an arbitrary file then you need to assert FileIOPermission for the plugin dll (full path), i.e.
private Assembly LoadAssemblyFromFile(string file)
{
FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.AllAccess, file);
perm.Assert();
return Assembly.LoadFile(file);
}

How to Load an Assembly to AppDomain with all references recursively?

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.

Categories

Resources