【发布时间】:2015-06-05 20:52:26
【问题描述】:
我正在开发一种基于插件的框架,该框架允许插件基于某种合同(接口)在彼此之间交换数据。
目前,Windows Service 可以通过两种方式加载插件:
- 在与托管插件的 Windows 服务相同的 AppDomain 中
- 在 Windows 服务通过命名管道 (WCF) 与之通信的另一个进程中。
这在大多数情况下都很好用。但是,在某些情况下,一个插件可能引用一个程序集,而另一个插件引用该程序集的较新版本。在这种情况下,我总是希望加载 newer 版本的依赖项,而不管先加载哪个插件。
这是文件夹结构:
- Windows 服务目录 (AppDomainBase)
- 插件
- 插件1
- Plugin1.dll
- SharedDependency.dll (1.0.0.0)
- 插件2
- Plugin2.dll
- SharedDependency.dll (1.0.1.0)
- 插件1
- 插件
我已经做了很多研究,并尝试了很多不同的东西。也就是说:
- 我无法通过 app.config 文件重定向程序集绑定。虽然这可行,但并不实用,因为我不提前知道所有依赖项,也无法将每个依赖项都添加到 app.config。
- 我无法使用 GAC
- 我不想加载同一个程序集的多个版本,只加载最新版本。
我已阅读有关 Assembly.Load、LoadFrom 和 LoadFile 的信息,并尝试使用所有这些。我仍然不是 100% 清楚 Load 和 LoadFrom 之间的区别。它们似乎都通过融合和从加载它们的目录中探测来自动加载每个插件的依赖项。
我目前的解决方案是搜索 AppDomainBase 的所有子目录,以查找并缓存每个插件文件夹中的所有 DLL。如果我不止一次遇到同一个程序集,我总是会跟踪最新版本及其位置。
然后,我通过调用 Assembly.LoadFile 来加载每个插件,这样融合就不会加载依赖项。我正在订阅 AppDomain.CurrentDomain.AssemblyResolve 事件。引发该事件时,我检查程序集的名称以确定应加载哪个程序集并预先缓存,然后通过调用 Assembly.Load 加载它。
private Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
try
{
Log(string.Format("Resolving: {0}", args.Name));
// Determine if the assembly is already loaded in the AppDomain. Only the name of the assembly is compared here.
var asm = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asm == null)
{
// The requsted assembly is not loaded in the current AppDomain
Log(string.Format("Assembly is not loaded in AppDomain: [{0}]", args.Name));
// Determine if the assembly is one that has already been found and cached
var asmName = _RefCandidates.Find(a => a.Name.FullName.Split(',')[0] == args.Name.Split(',')[0]);
if (asmName != null)
{
// The assembly exists in the cache, but has not been loaded. Load it.
Log(string.Format("Pre-loaded assembly found in cache. Loading: [{0}], [{1}]", asmName.Name, asmName.Name.CodeBase));
return Assembly.LoadFile(asmName.File.FullName);
}
}
else
Log(string.Format("Assembly is already loaded in AppDomain: [{0}], [{1}]", asm.GetName(), asm.GetName().CodeBase));
return asm;
}
catch (Exception ex)
{
Logger.Write(ex, LogEntryType.Error);
return null;
}
}
首先,完成我需要做的事情的最佳方法是什么,我做错了什么?
其次,如果链中的依赖项期望引用 GAC 中的某些内容,会发生什么情况?我认为它将不再被发现,因为我正在使用 LoadFile 并一起跳过融合。另外,我已经阅读了一些关于序列化不适用于 LoadFile 的内容。具体是什么?资源程序集呢?
此模型假设所有较新版本的依赖程序集都向后兼容,因为我将仅加载最新版本。
非常感谢任何输入。
【问题讨论】:
-
从不,从不,使用 LoadFile(),仅使用 LoadFrom。 Read this 缩小这个问题的范围。
-
感谢您的链接,那里有很好的信息。但是,您能否提供一些关于为什么永远不应使用 LoadFile 的见解?我知道它只加载请求的程序集,并没有对依赖项进行额外的探测。
标签: c# .net plugins .net-assembly assembly-resolution