• 通过应用程序域AppDomain加载和卸载程序集(转载)


    转自:http://www.cnblogs.com/wayfarer/archive/2004/09/29/47896.html

    通过AppDomain来实现程序集的卸载,这个思路是非常清晰的。由于在程序设计中,非特殊的需要,我们都是运行在同一个应用程序域中。由于程序集的卸载存在上述的缺陷,我们必须要关闭应用程序域,方可卸载已经装载的程序集。然而主程序域是不能关闭的,因此唯一的办法就是在主程序域中建立一个子程序域,通过它来专门实现程序集的装载。一旦要卸载这些程序集,就只需要卸载该子程序域就可以了,它并不影响主程序域的执行。

    不过现在看来,最主要的问题不是子程序域如何创建,关键是我们必须实现一种机制,来达到两个程序域之间完成通讯的功能。如果大家熟悉Remoting,就会想到这个问题不是和Remoting的机制有几分相似之处吗?那么答案就可以呼之欲出了,对了,就是使用代理的方法!不过与Remoting不同的是两个程序域之间的关系。因为子程序域是在主程序域中建立的,因此对该域的控制显然就与Remoting不相同了。

    我想先用一副图来表述实现的机制:

    说明:
    1、Loader类提供创建子程序域和卸载程序域的方法;
    2、RemoteLoader类提供装载程序集方法;
    3、Loader类获得RemoteLoader类的代理对象,并调用RemoteLoader类的方法;
    4、RemoteLoader类的方法在子程序域中完成;
    5、Loader类和RemoteLoader类均放在AssemblyLoader.dll程序集文件中;

    我们再来看代码:
    Loader类:

    SetRemoteLoaderObject()方法:

      private AppDomain domain = null;
      private Hashtable domains = new Hashtable();  
      private RemoteLoader rl = null;
    public RemoteLoader SetRemoteLoaderObject(string dllName)
    {
        AppDomainSetup setup 
    = new AppDomainSetup();            
        setup.ShadowCopyFiles 
    = "true";
        domain 
    = AppDomain.CreateDomain(dllName,null,setup);
                
        domains.Add(dllName,domain);    
        
    try
        
    {
                    rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap(
                    "AssemblyLoader.dll","AssemblyLoader.RemoteLoader");         
        }

        
    catch
        
    {
            
    throw new Exception();
        }

    }


    代码中的变量rl为RemoteLoader类对象,在Loader类中是其私有成员。SetRemoteLoaderObject()方法实际上提供了两个功能,一是创建了子程序域,第二则是获得了RemoteLoader类对象。

    请大家一定要注意语句:
    rl = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader");

    这条语句就是实现两个程序域之间通讯的关键。因为Loader类是在主程序域中,RemoteLoader类则是在子程序域中。如果我们在Loader类即主程序域中显示实例化RemoteLoader类对象rl,此时调用rl的方法,实际上是在主程序域中调用的。因此,我们必须使用代理的方式,来获得rl对象,这就是CreateInstanceFromAndUnwrap方法的目的。其中参数一为要创建类对象的程序集文件名,参数二则是该类的类型名。

    CreateCreateInstanceFromAndUnwrap方法有多个重载。代码中的调用方式是当RemoteLoader类为默认构造函数时的其中一种重载。如果RemoteLoader类的构造函数有参数,则方法应改为:

    object[] parms = {dllName};
    BindingFlags bindings 
    = BindingFlags.CreateInstance |
    BindingFlags.Instance 
    | BindingFlags.Public;
    rl 
    = (AssemblyLoader.RemoteLoader)domain.CreateInstanceFromAndUnwrap("AssemblyLoader.dll","AssemblyLoader.RemoteLoader",true,bindings,
    null,parms,null,null,null);

    详细的调用方式可以参考MSDN。

    以下Loader类的Unload方法和LoadAssembly方法():

    public Assembly LoadAssembly(string dllName)
    {
        
    try
        
    {
            SetRemoteLoaderObject(dllName);
            
    return rl.LoadAssembly(dllName);
        }

        
    catch (Exception)
        
    {
            
    throw new AssemblyLoadFailureException();
        }

    }
    public void Unload(string dllName)
    {
        
    if (domains.ContainsKey(dllName))
        
    {
            AppDomain appDomain 
    = (AppDomain)domains[dllName];
            AppDomain.Unload(appDomain);
            domains.Remove(dllName);
        }
                
    }

    当我们调用Unload方法时,则程序域domain加载的程序集也将随着而被卸载。LoadAssembly方法中的异常AssemblyLoadFailureException为自定义异常:

        public class AssemblyLoadFailureException:Exception
        
    {
            
    public AssemblyLoadFailureException():base()
            
    {            
            }


            
    public override string Message
            
    {
                
    get
                
    {
                    
    return "Assembly Load Failure";
                }

            }


        }


    既然在Loader类获得的RemoteLoader类实例必须通过代理的方式,因此该类对象必须支持被序列化。所以我们可以令该类派生MarshalByRefObject。RemoteLoader类的代码:

        public class RemoteLoader:MarshalByRefObject
        
    {
            
    public RemoteLoader(string dllName)
            
    {
                
    if (assembly == null)
                
    {
                    assembly 
    = Assembly.LoadFrom(dllName);
                }

            }
            

            
    private Assembly assembly = null;

            
    public Assembly LoadAssembly(string dllName)
            
    {
                
    try
                
    {
                    assembly 
    = Assembly.LoadFrom(dllName);                
                    
    return assembly;
                }

                
    catch (Exception)
                
    {
                    
    throw new AssemblyLoadFailureException();
                }

            }

        }


    通过上述的两个类,我们就可以实现程序集的加载和卸载。另外,为了保证应用程序域的对象在内存中被清除,应该令这两个类都实现IDisposable接口,和实现Dispose()方法。

    然而在实际的操作过程中,我发现在RemoteLoader类的LoadAssembly方法,是存在遗患的。在我的LoadAssembly方法中,会返回一个Assembly对象。令我百思不得其解的是,虽然都是Assembly对象,但在加载某些程序集并返回Assembly时,在Loader类中会抛出SerializationException异常,并报告反序列化的对象状态不足。这个异常是在序列化获反序列化过程中发生的。我反复比较了两个程序集,一个可以正常加载并序列化,一个会抛出如上异常。会抛出异常的程序集并没有什么特殊之处,且我在程序中的其他地方也没有重复加载该程序集。这是一个疑问!!

    不过通常我们在RemoteLoader类中,要实现的方法并非返回一个Assembly对象,而是通过反射加载程序集后,创建该程序集的对象。由于类对象都为object类型,此时序列化就不会出现问题。在我的项目中,因为要获得程序集的版本号,比较版本号在确定是否需要更新,因此我在RemoteLoader类中,只需要在加载程序集后,返回程序集的版本号字符串类型就可以了。字符串类型是绝对支持序列化的。

    AssemlbyLoader.Dll的源代码可以点击这里获得。在应用程序中,显示添加对该程序集的引用,然后实例化Loader类对象,来调用该方法即可。我还做了一个简单的测试程序,用的是LoadAssembly方法。大家可以测试一下,是否如我所说,对于某些程序集,可能会抛出序列化的异常!?

  • 相关阅读:
    ABAP——动态排序内表
    通过jmeter往kafka写入数据
    清空kafka全部数据
    redis命令
    Eureka的工作原理
    Spring Cloud Ribbon 原理解析
    Redis单实例数据迁移到集群
    Spring Cloud Hystrix
    ElasticSearch
    关于Java导出100万行数据到Excel的优化方案
  • 原文地址:https://www.cnblogs.com/johnwonder/p/1673300.html
Copyright © 2020-2023  润新知