I'm trying to build a small tool for comparing types in a bunch of assemblies. For this purpose I created two subfolders and put the respective dlls there:
..\Dlls\v1.1
..\Dlls\v1.2
where ..
is the application folder
I also created a proxy object:
public class ProxyDomain : MarshalByRefObject
{
public Assembly LoadFile(string assemblyPath)
{
try
{
//Debug.WriteLine("CurrentDomain = " + AppDomain.CurrentDomain.FriendlyName);
return Assembly.LoadFile(assemblyPath);
}
catch (FileNotFoundException)
{
return null;
}
}
}
and used it for loading in the following routine that should load an dll and get all types declared in it:
private static HashSet<Type> LoadAssemblies(string version)
{
_currentVersion = version;
var path = Path.Combine(Environment.CurrentDirectory, Path.Combine(_assemblyDirectory, version));
var appDomainSetup = new AppDomainSetup
{
ApplicationBase = Environment.CurrentDirectory,
};
var evidence = AppDomain.CurrentDomain.Evidence;
var appDomain = AppDomain.CreateDomain("AppDomain" + version, evidence, appDomainSetup);
var proxyDomainType = typeof(ProxyDomain);
var proxyDomain = (ProxyDomain)appDomain.CreateInstanceAndUnwrap(proxyDomainType.Assembly.FullName, proxyDomainType.FullName);
_currentProxyDomain = proxyDomain;
var assemblies = new HashSet<Type>();
var files = Directory.GetFiles(path, "*.dll");
foreach (var file in files)
{
try
{
var assembly = proxyDomain.LoadFile(file);
if (assembly != null)
{
assemblies.UnionWith(assembly.DefinedTypes.Where(t => t.IsPublic));
}
}
catch (Exception)
{
}
}
return assemblies;
}
So far nothing unusual... but it didn't work like that in this case (probably because of the subfolders) so I searched a bit and found that a settting in the app.config
might help so I tried to add two probing paths:
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dlls\v1.1;Dlls\v1.2" />
</assemblyBinding>
</runtime>
Now there wasn't any FileNotFoundExpection
s anymore but as the dlls have the same names it loaded only dlls from the first subdirectory (v1.1
) into both domains so I removed it and instead tried to implement the AppDomain.CurrentDomain.AssemblyResolve
event handler like that:
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var path = Path.Combine(Environment.CurrentDirectory, Path.Combine(_assemblyDirectory, _currentVersion));
path = Path.Combine(path, e.Name.Split(',').First());
path = path + ".dll";
var assembly = _currentProxyDomain.LoadFile(path);
return assembly;
};
But unfortunatelly with this event handler I created an infinite loop and it calls itself each time it tries to load a dll it cannot find. I have no more ideas what else I could try.
UPDATE-1
As @SimonMourier suggested I tried to use a custom .config
for my new AppDomains and created two more *.config
s like:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Dlls\v1.1" />
</assemblyBinding>
</runtime>
</configuration>
I named them v1.1.config
and v1.2.config
. Then I set the new ConfigurationFile
property:
var appDomainSetup = new AppDomainSetup
{
ApplicationBase = Environment.CurrentDirectory,
ConfigurationFile = Path.Combine(Environment.CurrentDirectory, string.Format("{0}.config", version)),
};
I've set the option Copy to Output Directory
to Copy always
;-)
It didn't work so I googled and tried another suggestion:
AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", appDomainSetup.ConfigurationFile);
but this didn't help either. Still FileNotFoundException
like my custom configs weren't there.
Using the SetConfigurationBytes
method instead had also no effect:
var domainConfig = @"
<configuration>
<startup>
<supportedRuntime version=""v4.0"" sku="".NETFramework,Version=v4.5"" />
</startup>
<runtime>
<assemblyBinding xmlns=""urn:schemas-microsoft-com:asm.v1"">
<probing privatePath=""Dlls\{0}"" />
</assemblyBinding>
</runtime>
</configuration>";
domainConfig = string.Format(domainConfig, version).Trim();
var probingBytes = Encoding.UTF8.GetBytes(domainConfig);
appDomainSetup.SetConfigurationBytes(probingBytes);
However if I call the GetData
method the new appdomain uses the custom .config:
Debug.WriteLine("Current config: " + AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE"));
It outputs the path that I set via ConfigurationFile
UPDATE-2
This is really confusing. The Stack Trace reveals that despite of what the GetData
returs the Assembly.LoadFile
still uses the original .config:
=== LOG: This bind starts in default load context. LOG: Using application configuration file:
C:[...]\bin\Debug\MyApp.exe.Config
LOG: Using host configuration file: LOG: Using machine configuration file from
C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.
UPDATE-3
OK, I did some more experimenting and found out that by implementing @SimonMourier suggestion it indeed worked. The FileNotFoundException
wasn't thrown by the LoadFile
method in the ProxyDomain
class but in the Main
method of my app. I gues the assembly and the types are allowed to live only within the ProxyDomain
context and cannot be transfered into the main domain as I have tried.
public IEnumerable<Type> LoadFile(string assemblyPath)
{
//try
{
// does't throw any exceptions
var assembly = Assembly.LoadFile(assemblyPath);
// returning the assembly itself or its types will throw an exception in the main application
return assembly.DefinedTypes;
}
//catch (FileNotFoundException)
{
// return null;
}
}
Method in the main domain:
private static HashSet<Type> LoadAssemblies(string version)
{
// omitted
foreach (var file in files)
{
//try
{
// the exception occurs here, when transfering types between domains:
var types = proxyDomain.LoadFile(file);
assemblies.UnionWith(types.Where(t => t.IsPublic));
}
}
// omitted
}
I'll need to rewrite the comparison algorithm now by at least the assemblies can be loaded ;-)
If you don't want to recompile the assemblies with a different strong name, than you can load them into different AppDomains, with different setup configuration, and different .config files, exactly like IIS does with multiple web sites, each one in its one AppDomain, completely independent, all hosted in only one AppPool process - w3wp.exe.
Strong name your dll . Make 2 exe to run separate app domain.
Otherwise there is no way to reference same dll name twice. You have to rename new dll with _new.dll and then you can use it with fully qualified names pace class method name format.
Meaning rename all v2 dll
来源:https://stackoverflow.com/questions/32857789/how-can-i-load-two-versions-of-same-assemblies-into-two-different-domains-from-t