How can I load two versions of same assemblies into two different domains from two different subfolders?

时光怂恿深爱的人放手 提交于 2019-12-01 05:36:08

问题


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 FileNotFoundExpections 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 *.configs 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 ;-)


回答1:


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.




回答2:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!