使用Assembly.Load,Assembly.LoadFile

时间:2015-06-05 20:52:26

标签: c# .net plugins .net-assembly assembly-resolution

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

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

  1. 在与托管插件的Windows服务相同的AppDomain中
  2. 在另一个Windows服务通过命名管道(WCF)进行通信的过程中。
  3. 大部分时间都很好用。但是,在某些情况下,一个插件可能引用程序集,而另一个插件引用较新版本的程序集。在这种情况下,我总是希望加载依赖项的较新版本,而不管首先加载哪个插件。

    这是文件夹结构:

    • Windows服务目录(AppDomainBase)
      • 插件
        • Plugin1
          • Plugin1.dll
          • SharedDependency.dll(1.0.0.0)
        • Plugin2
          • Plugin2.dll
          • SharedDependency.dll(1.0.1.0)

    我已经做了很多研究,尝试过很多不同的事情。那说:

    1. 我无法通过app.config文件重定向程序集绑定。虽然这有效,但它不实用,因为我不提前知道所有依赖项,也无法将每一个依赖项添加到app.config。
    2. 我无法使用GAC
    3. 我不想加载同一个程序集的多个版本,只是最新版本。
    4. 我已阅读有关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的内容。具体是什么?资源组件怎么样?

      此模型假设所有较新版本的从属程序集都向后兼容,因为我只会加载最新版本。

      非常感谢任何输入。

1 个答案:

答案 0 :(得分:0)

如果此程序集的分辨率未失败(仅在此情况下会引发AssemblyResolve),则无法手动加载程序集。 因此,您的插件架构无法使用Assembly.Load *方法实现。

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

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

另请参阅此link有关程序集加载上下文的信息,以了解为什么没有AssemblyResolve事件的Assembly.Load将无效。