65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (18投票s)

2012 年 9 月 5 日

CPOL

3分钟阅读

viewsIcon

98493

在本文中,我将概括和扩展“在单独目录中加载程序集”,并添加一些辅助功能。

引言

那些尝试加载程序集以使用反射进行检查,并将加载到在您完成反射后立即卸载的应用程序域中的人,肯定知道这种体验类似于在魔幻领域中行走,在那里什么都行不通,您触摸的一切都会损坏。我的问题是通过 Sacha Barber 在他的文章将程序集加载到单独目录中的新 AppDomain 中编写的一段代码解决的。但是他的示例太特殊了,因此在本文中,我将对其进行概括并使用一些辅助功能对其进行扩展。

问题

当我想执行以下操作时,我开始遇到加载程序集的问题

  • 通过反射从各种程序集中读取一些信息
  • 这些程序集位于磁盘上的各个位置
  • 只需要加载程序集即可执行反射,因此
  • 每个程序集都将加载到单独的 AppDomain 中,以便在检查完成后可以卸载它们

无论我尝试了多少,加载程序集都因各种原因而失败,具体取决于我尝试的解决方案。最终,我发现了 Sacha Barber 的文章,这是一个改变游戏规则的方法。

解决方案

问题的解决方案基于

  • 创建一个程序集代理(或包装器),该代理派生自 MarshalByRefObject,以便 CLR 可以通过引用跨 AppDomain 边界对其进行封送
  • 在此代理中加载程序集 (Assembly.ReflectionOnlyLoadFrom)
  • 在此代理内部执行反射并返回所需的数据
  • 创建一个临时 AppDomain 并在该 AppDomain 中实例化程序集代理(AppDomain.CreateInstanceFrom
  • 在完成反射后立即卸载 AppDomain

但是,您必须记住,以这种方式加载的程序集的反射只能在代理(从 MarshalByRefObject 派生的代理)内部进行。不可能返回任何“反射对象”(System.Reflection 命名空间中定义的任何内容,例如 TypeMethodInfo 等)。尝试从另一个 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 日:初始版本
© . All rights reserved.