如何从两个不同的子文件夹将两个版本的相同程序集加载到两个不同的域中?

时间:2015-09-30 04:25:51

标签: c# .net dll .net-4.5 appdomain

我正在尝试构建一个小工具来比较一堆程序集中的类型。为此,我创建了两个子文件夹并将各自的dll放在那里:

  • ..\Dlls\v1.1
  • ..\Dlls\v1.2

其中..是应用程序文件夹

我还创建了一个代理对象:

public class ProxyDomain : MarshalByRefObject
{
    public Assembly LoadFile(string assemblyPath)
    {
        try
        {
            //Debug.WriteLine("CurrentDomain = " + AppDomain.CurrentDomain.FriendlyName);
            return Assembly.LoadFile(assemblyPath);
        }
        catch (FileNotFoundException)
        {
            return null;
        }
    }
}

并将其用于加载以下例程,该例程应加载一个dll并获取其中声明的所有类型:

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;
}

到目前为止没有什么不寻常的......但在这种情况下它并没有像这样工作(可能是因为子文件夹)所以我搜索了一下,发现app.config的设置可能会有所帮助我试图添加两条探测路径:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <probing privatePath="Dlls\v1.1;Dlls\v1.2" />
  </assemblyBinding>
</runtime>

现在已经没有任何FileNotFoundExpection了,但由于dll具有相同的名称,它只将dll从第一个子目录(v1.1)加载到两个域中,因此我删除了它而是试图像这样实现AppDomain.CurrentDomain.AssemblyResolve事件处理程序:

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;
};

但不幸的是,我使用这个事件处理程序创建了一个无限循环,每次它尝试加载它找不到的dll时,调用自身。我没有更多的想法我还能尝试什么。

UPDATE-1

正如@SimonMourier建议我尝试为我的新AppDomains使用自定义.config,并再创建两个*.config,如:

<?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>

我将它们命名为v1.1.configv1.2.config。然后我设置 new ConfigurationFile属性:

var appDomainSetup = new AppDomainSetup
{
    ApplicationBase = Environment.CurrentDirectory,
    ConfigurationFile = Path.Combine(Environment.CurrentDirectory, string.Format("{0}.config", version)),        
};

我已将选项Copy to Output Directory设为Copy always; - )

它没有用,所以我用Google搜索并尝试了另一个建议:

AppDomain.CurrentDomain.SetData("APP_CONFIG_FILE", appDomainSetup.ConfigurationFile);

但这也没有帮助。仍然FileNotFoundException像我的自定义配置那样。

使用SetConfigurationBytes方法也没有效果:

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);

但是,如果我调用GetData方法,新的appdomain将使用自定义.config:

Debug.WriteLine("Current config: " + AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE"));

它输出我通过ConfigurationFile

设置的路径

UPDATE-2

这真令人困惑。 Stack Trace显示,尽管GetData重新调整Assembly.LoadFile仍然使用原始.config:

  

===日志:此绑定在默认加载上下文中启动。日志:使用应用程序配置文件:

     

C:[...] \ BIN \调试\ MyApp.exe.Config

     

日志:使用主机配置文件:日志:使用

中的机器配置文件      

C:\的Windows \ Microsoft.NET \框架\ v4.0.30319 \ CONFIG \ machine.config中

UPDATE-3

好的,我做了一些实验,发现通过实施@SimonMourier建议确实有效。 FileNotFoundException类中的LoadFile方法引用了ProxyDomain,但我的应用的Main方法却没有引发ProxyDomain。我猜测程序集,类型只允许在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; } } 上下文中生存,并且不能像我尝试的那样转移到主域中。

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
}

主域中的方法:

angular
.module('app', [])
.controller('AppCtrl', function($scope) {
  $scope.images = [
    { src : 'http://lorempixel.com/400/200/sports/1' },
    { src : 'http://lorempixel.com/400/200/sports/2' },
    { src : 'http://lorempixel.com/400/200/sports/3' },
    { src : 'http://lorempixel.com/400/200/sports/4' },
    { src : 'http://lorempixel.com/400/200/sports/5' }
  ];

  $scope.result = 60;
  $scope.computedIndex = Math.floor($scope.result / 100);
});

我现在需要重写比较算法,至少可以加载程序集; - )

2 个答案:

答案 0 :(得分:3)

如果您不想使用不同的强名称重新编译程序集,则可以将它们加载到具有不同安装配置和不同.config文件的不同AppDomain中,就像IIS与多个网站一样,每个AppDomain中的一个,完全独立,全部只托管在一个AppPool进程中 - w3wp.exe。

答案 1 :(得分:0)

强烈的名字你的DLL。 使2 exe运行单独的应用程序域。

否则无法两次引用相同的dll名称。 您必须使用_new.dll重命名新的dll,然后您可以使用完全限定名称的起搏级方法名称格式。

意思是重命名所有v2 dll