如何将两个版本的相同程序集从两个不同的子文件夹加载到两个不同的域中? [英] How can I load two versions of same assemblies into two different domains from two different subfolders?

查看:20
本文介绍了如何将两个版本的相同程序集从两个不同的子文件夹加载到两个不同的域中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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

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>

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

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

但不幸的是,我用这个事件处理程序创建了一个无限循环,每次尝试加载它找不到的 dll 时,它都会调用自己.我不知道我还能尝试什么.

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.

正如@SimonMourier 所建议的那样,我尝试为我的新 AppDomain 使用自定义 .config 并创建了另外两个 *.config,例如:

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>

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

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

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

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

但这也无济于事.仍然 FileNotFoundException 就像我的自定义配置不存在一样.

but this didn't help either. Still FileNotFoundException like my custom configs weren't there.

使用 SetConfigurationBytes 方法也没有效果:

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

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

However if I call the GetData method the new appdomain uses the custom .config:

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

它输出我通过ConfigurationFile

这真的很令人困惑.堆栈跟踪显示,尽管 GetData 返回了什么,Assembly.LoadFile 仍然使用原始 .config:

This is really confusing. The Stack Trace reveals that despite of what the GetData returs the Assembly.LoadFile still uses the original .config:

=== LOG:此绑定在默认加载上下文中开始.LOG:使用应用程序配置文件:

=== LOG: This bind starts in default load context. LOG: Using application configuration file:

C:[...]\bin\Debug\MyApp.exe.Config

C:[...]\bin\Debug\MyApp.exe.Config

LOG:使用主机配置文件:LOG:使用机器配置文件来自

LOG: Using host configuration file: LOG: Using machine configuration file from

C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.

C:\Windows\Microsoft.NET\Framework\v4.0.30319\config\machine.config.


更新-3

好的,我做了更多的实验,发现通过实施@SimonMourier 的建议确实有效.FileNotFoundException 不是由 ProxyDomain 类中的 LoadFile 方法抛出的,而是在我的应用程序的 Main 方法中抛出的.我猜程序集和类型只允许存在于 ProxyDomain 上下文中,不能像我尝试的那样转移到主域中.


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

主域中的方法:

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

推荐答案

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

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.

这篇关于如何将两个版本的相同程序集从两个不同的子文件夹加载到两个不同的域中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆