【问题标题】:Proper dependency loading for plug-ins using Assembly.Load, Assembly.LoadFile使用 Assembly.Load、Assembly.LoadFile 为插件正确加载依赖项
【发布时间】:2015-06-05 20:52:26
【问题描述】:

我正在开发一种基于插件的框架,该框架允许插件基于某种合同(接口)在彼此之间交换数据。

目前,Windows Service 可以通过两种方式加载插件:

  1. 在与托管插件的 Windows 服务相同的 AppDomain 中
  2. 在 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。
  2. 我无法使用 GAC
  3. 我不想加载同一个程序集的多个版本,只加载最新版本。

我已阅读有关 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


【解决方案1】:

如果此程序集的解析没有失败(仅在这种情况下 AssemblyResolve 被引发),则您不能手动加载程序集。 因此,您的插件架构无法使用 Assembly.Load* 方法实现。

现在有两条路。
首先(不推荐):以某种方式消除限制(例如在开始时使用 Assembly.Load,并且在任何时候需要一个对象,搜索 CurrentDomain 程序集,选择正确的程序集并通过反射构造对象并使用它们)。
第二(推荐):检查您最初的问题并寻找易于实施和维护的解决方案。

看看Managed Extensibility Framework 为插件架构提供了什么。
SharpDevelop 的人写了a book,告诉我们他们的插件树。
找到自己的路。

另请参阅link 关于程序集加载上下文,以了解为什么没有 AssemblyResolve 事件的 Assembly.Load 将不起作用。

【讨论】:

  • 我不明白。如果我使用 Assembly.LoadFile 加载插件,我能够处理 AssemblyResolve 事件,因为融合似乎不用于加载依赖项。这似乎在我上面的示例中有效。我实际上将上面的代码从使用 Assembly.Load 更改为 Assembly.LoadFile。 LoadFile 对序列化和资源程序集还有哪些其他限制?此外,似乎我可以在 AppDomain 基础子文件夹中的 DLL 上使用 Assembly.Load 方法,并且它们的依赖项也被找到并加载。我以为这就是 LoadFrom 的用途……?
  • 这行得通,因为查找程序集的标准程序失败。您没有直接在 AppDomainBase 文件夹中的 SharedDependency.dll ,对吗?将您的 SharedDependency.dll 放入 GAC,您将无法手动解决它。实际上,它似乎已经像 Dll 地狱了。很抱歉没有回答您的问题,但我想我们在这里处理 XY 问题meta.stackexchange.com/a/66378
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-27
相关资源
最近更新 更多