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

标签 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 具有相同的名称,它仅将第一个子目录 (v1.1) 中的 dll 加载到两个域中所以我删除了它,而是尝试像这样实现 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 时,它都会调用自身。我不知道我还能尝试什么。


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

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

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 方法,新的应用程序域将使用自定义 .config:

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

它输出我通过ConfigurationFile设置的路径


更新-2

这真是令人费解。堆栈跟踪显示,尽管 GetData 返回了什么,Assembly.LoadFile 仍然使用原始的 .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.


更新-3

好的,我做了一些更多的实验,发现通过实现@SimonMourier 的建议它确实有效。 FileNotFoundException 不是由 ProxyDomain 类中的 LoadFile 方法抛出,而是在我的应用程序的 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
}

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

最佳答案

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

关于c# - 如何将两个版本的相同程序集从两个不同的子文件夹加载到两个不同的域中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32857789/

相关文章:

c# - Samsung Gear VR - 将武器附加到玩家身上

c# - 在大型 WPF 项目中使用大型 WinForms 项目的一部分

c# - 如何禁用 WPF ListBox 中的水平滚动?

c# - 如何避免在变量赋值的情况下调用冗余?

c# - 为什么我必须在 DataGridViewRow 上调用 Cast<> 扩展方法?

c++ - std::type_index 跨 DLL 是否安全

c++ - 无法让 Ocean Optics OmniDriver 工作

c# - .NET 的整数不就是简单地基于补码系统吗?

C#:嵌套类的构造函数使 "inaccessible due to protection level"

c# - Testand - 通过使用 C DLL 的 C# 包装器使用 C# DLL