如何将两个版本的相同程序集从两个不同的子文件夹加载到两个不同的域中?
How can I load two versions of same assemblies into two different domains from two different subfolders?
我正在尝试构建一个小工具来比较一堆程序集中的类型。为此,我创建了两个子文件夹并将相应的 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.config
和 v1.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
方法,新的 appdomain 使用自定义 .config:
Debug.WriteLine("Current config: " + AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE"));
输出我通过ConfigurationFile
设置的路径
更新 2
这真是令人费解。 Stack Trace 显示尽管 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
class 中的 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
}
我现在需要重写比较算法,至少可以加载程序集 ;-)
强命名您的 dll。
制作 2 个 exe 到 运行 单独的应用程序域。
否则无法引用同一个dll名称两次。
您必须使用 _new.dll 重命名新的 dll,然后您可以使用完全限定名称 pace class 方法名称格式。
意思是重命名所有 v2 dll
如果您不想使用不同的强名称重新编译程序集,您可以将它们加载到不同的 AppDomains、不同的安装配置和不同的 .config 文件中,就像 IIS 对多个网站所做的一样,每个都在其一个 AppDomain 中,完全独立,全部托管在一个 AppPool 进程中 - w3wp.exe.
我正在尝试构建一个小工具来比较一堆程序集中的类型。为此,我创建了两个子文件夹并将相应的 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.config
和 v1.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
方法,新的 appdomain 使用自定义 .config:
Debug.WriteLine("Current config: " + AppDomain.CurrentDomain.GetData("APP_CONFIG_FILE"));
输出我通过ConfigurationFile
更新 2
这真是令人费解。 Stack Trace 显示尽管 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
class 中的 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
}
我现在需要重写比较算法,至少可以加载程序集 ;-)
强命名您的 dll。 制作 2 个 exe 到 运行 单独的应用程序域。
否则无法引用同一个dll名称两次。 您必须使用 _new.dll 重命名新的 dll,然后您可以使用完全限定名称 pace class 方法名称格式。
意思是重命名所有 v2 dll
如果您不想使用不同的强名称重新编译程序集,您可以将它们加载到不同的 AppDomains、不同的安装配置和不同的 .config 文件中,就像 IIS 对多个网站所做的一样,每个都在其一个 AppDomain 中,完全独立,全部托管在一个 AppPool 进程中 - w3wp.exe.