从任意位置将程序集加载到新的 AppDomain






4.95/5 (18投票s)
在本文中,我将概括和扩展“在单独目录中加载程序集”,并添加一些辅助功能。
引言
那些尝试加载程序集以使用反射进行检查,并将加载到在您完成反射后立即卸载的应用程序域中的人,肯定知道这种体验类似于在魔幻领域中行走,在那里什么都行不通,您触摸的一切都会损坏。我的问题是通过 Sacha Barber 在他的文章将程序集加载到单独目录中的新 AppDomain 中编写的一段代码解决的。但是他的示例太特殊了,因此在本文中,我将对其进行概括并使用一些辅助功能对其进行扩展。
问题
当我想执行以下操作时,我开始遇到加载程序集的问题
- 通过反射从各种程序集中读取一些信息
- 这些程序集位于磁盘上的各个位置
- 只需要加载程序集即可执行反射,因此
- 每个程序集都将加载到单独的
AppDomain
中,以便在检查完成后可以卸载它们
无论我尝试了多少,加载程序集都因各种原因而失败,具体取决于我尝试的解决方案。最终,我发现了 Sacha Barber 的文章,这是一个改变游戏规则的方法。
解决方案
问题的解决方案基于
- 创建一个程序集代理(或包装器),该代理派生自
MarshalByRefObject
,以便 CLR 可以通过引用跨AppDomain
边界对其进行封送 - 在此代理中加载程序集 (
Assembly.ReflectionOnlyLoadFrom
) - 在此代理内部执行反射并返回所需的数据
- 创建一个临时
AppDomain
并在该AppDomain
中实例化程序集代理(AppDomain.CreateInstanceFrom
) - 在完成反射后立即卸载
AppDomain
但是,您必须记住,以这种方式加载的程序集的反射只能在代理(从 MarshalByRefObject
派生的代理)内部进行。不可能返回任何“反射对象”(System.Reflection
命名空间中定义的任何内容,例如 Type
、MethodInfo
等)。尝试从另一个 AppDomain
(调用者的域)访问这些对象将导致异常。
概括和扩展
我对 Sacha Barber 的代码做了两件事
- 概括了程序集代理,使其可以对程序集执行任何反射查询。方法
Reflect()
将任何带有Assembly
类型参数的函数作为参数,并将结果返回给调用者(请参阅下面的示例)。 - 添加了一个代理管理器,该管理器将程序集加载到
AppDomains
中,执行查询并卸载AppDomains
。
这是一个使用此管理器的简单示例。
var assemblyPath = "..."; // your assembly path here
var manager = new AssemblyReflectionManager();
var success = manager.LoadAssembly(assemblyPath, "demodomain");
var results = manager.Reflect(assemblyPath, (a) =>{
var names = new List<string>();
var types = a.GetTypes();
foreach (var t in types)
names.Add(t.Name);
return names;
});
foreach(var name in results)
Console.WriteLine(name);
manager.UnloadAssembly(assemblyPath);
AssemblyReflectionManager
包含以下 public
接口
-
bool LoadAssembly(string assemblyPath, string domainName).
将程序集加载到应用程序域中。如果程序集路径已被加载,则此函数将失败。
-
bool UnloadAssembly(string assemblyPath)
通过卸载加载该程序集的
AppDomain
来卸载已加载的程序集。如果在指定的程序集中,同一AppDomain
中加载了更多程序集,则此函数将失败。您仍然可以通过调用UnloadDomain
来卸载程序集。 -
bool UnloadDomain(string domainName)
从进程中卸载应用程序域。
-
TResult Reflect<TResult>(string assemblyPath, Func<Assembly, TResult> func)
对加载的程序集执行反射并返回结果。不可能返回
System.Reflection
命名空间中的任何类型,因为它们在代理的AppDomain
之外无效。
代码
以下提供了代理和管理器的代码(已缩短注释)。
public class AssemblyReflectionProxy : MarshalByRefObject
{
private string _assemblyPath;
public void LoadAssembly(String assemblyPath)
{
try
{
_assemblyPath = assemblyPath;
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
// Continue loading assemblies even if an assembly
// cannot be loaded in the new AppDomain.
}
}
public TResult Reflect<TResult>(Func<Assembly, TResult> func)
{
DirectoryInfo directory = new FileInfo(_assemblyPath).Directory;
ResolveEventHandler resolveEventHandler =
(s, e) =>
{
return OnReflectionOnlyResolve(
e, directory);
};
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
var assembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault
(a => a.Location.CompareTo(_assemblyPath) == 0);
var result = func(assembly);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;
return result;
}
private Assembly OnReflectionOnlyResolve(ResolveEventArgs args, DirectoryInfo directory)
{
Assembly loadedAssembly =
AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
.FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name,
StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
AssemblyName assemblyName =
new AssemblyName(args.Name);
string dependentAssemblyFilename =
Path.Combine(directory.FullName,
assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(
dependentAssemblyFilename);
}
return Assembly.ReflectionOnlyLoad(args.Name);
}
}
public class AssemblyReflectionManager : IDisposable
{
Dictionary<string, AppDomain> _mapDomains = new Dictionary<string, AppDomain>();
Dictionary<string, AppDomain> _loadedAssemblies = new Dictionary<string, AppDomain>();
Dictionary<string, AssemblyReflectionProxy> _proxies =
new Dictionary<string, AssemblyReflectionProxy>();
public bool LoadAssembly(string assemblyPath, string domainName)
{
// if the assembly file does not exist then fail
if (!File.Exists(assemblyPath))
return false;
// if the assembly was already loaded then fail
if (_loadedAssemblies.ContainsKey(assemblyPath))
{
return false;
}
// check if the appdomain exists, and if not create a new one
AppDomain appDomain = null;
if (_mapDomains.ContainsKey(domainName))
{
appDomain = _mapDomains[domainName];
}
else
{
appDomain = CreateChildDomain(AppDomain.CurrentDomain, domainName);
_mapDomains[domainName] = appDomain;
}
// load the assembly in the specified app domain
try
{
Type proxyType = typeof(AssemblyReflectionProxy);
if (proxyType.Assembly != null)
{
var proxy =
(AssemblyReflectionProxy)appDomain.
CreateInstanceFrom(
proxyType.Assembly.Location,
proxyType.FullName).Unwrap();
proxy.LoadAssembly(assemblyPath);
_loadedAssemblies[assemblyPath] = appDomain;
_proxies[assemblyPath] = proxy;
return true;
}
}
catch
{}
return false;
}
public bool UnloadAssembly(string assemblyPath)
{
if (!File.Exists(assemblyPath))
return false;
// check if the assembly is found in the internal dictionaries
if (_loadedAssemblies.ContainsKey(assemblyPath) &&
_proxies.ContainsKey(assemblyPath))
{
// check if there are more assemblies loaded in the same app domain;
// in this case fail
AppDomain appDomain = _loadedAssemblies[assemblyPath];
int count = _loadedAssemblies.Values.Count(a => a == appDomain);
if (count != 1)
return false;
try
{
// remove the appdomain from the dictionary and unload it from the process
_mapDomains.Remove(appDomain.FriendlyName);
AppDomain.Unload(appDomain);
// remove the assembly from the dictionaries
_loadedAssemblies.Remove(assemblyPath);
_proxies.Remove(assemblyPath);
return true;
}
catch
{
}
}
return false;
}
public bool UnloadDomain(string domainName)
{
// check the appdomain name is valid
if (string.IsNullOrEmpty(domainName))
return false;
// check we have an instance of the domain
if (_mapDomains.ContainsKey(domainName))
{
try
{
var appDomain = _mapDomains[domainName];
// check the assemblies that are loaded in this app domain
var assemblies = new List<string>();
foreach (var kvp in _loadedAssemblies)
{
if (kvp.Value == appDomain)
assemblies.Add(kvp.Key);
}
// remove these assemblies from the internal dictionaries
foreach (var assemblyName in assemblies)
{
_loadedAssemblies.Remove(assemblyName);
_proxies.Remove(assemblyName);
}
// remove the appdomain from the dictionary
_mapDomains.Remove(domainName);
// unload the appdomain
AppDomain.Unload(appDomain);
return true;
}
catch
{
}
}
return false;
}
public TResult Reflect<TResult>(string assemblyPath, Func<Assembly, TResult> func)
{
// check if the assembly is found in the internal dictionaries
if (_loadedAssemblies.ContainsKey(assemblyPath) &&
_proxies.ContainsKey(assemblyPath))
{
return _proxies[assemblyPath].Reflect(func);
}
return default(TResult);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~AssemblyReflectionManager()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
foreach (var appDomain in _mapDomains.Values)
AppDomain.Unload(appDomain);
_loadedAssemblies.Clear();
_proxies.Clear();
_mapDomains.Clear();
}
}
private AppDomain CreateChildDomain(AppDomain parentDomain, string domainName)
{
Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain(domainName, evidence, setup);
}
}
附加阅读材料
历史
- 2012 年 9 月 5 日:初始版本