AppDomain 动态加载及数据交互

前一个项目,有机会学了AppDomian基本概念。把学习心得写下来。范例可以在这里下载

AppDomain的作用

         AppDomain 也叫应用程序域,是CLR创建的,用来规范代码的执行范围,并提供独立的错误隔离机制。因此,每个应用程序包可以包含一个或多个AppDomain,每个AppDomain能够独立卸载及管理Assembly。

         因为AppDomain也是独立存在互不影响,而且创建一个AppDomain和跨AppDomain的开销都比进程要低得多,所以常常使用独立AppDomain来提供服务,避免由于程序某部分出错而导致整个程序崩溃。

示例简述

        通过调用不同AppDomain中SayHi的对象 实现了 中文,英文,同时使用中英文的SayHi方法,以及异常问题。

  1. MultiAppDomain 项目:是默认程序域的所在项目。用于创建副AppDomain,并且调用副AppDomain里面的服务实现SayHi的调用
  2.  Interface项目。只有一个接口,用于规范SayHi的调用方式。
  3.  SayHiChinese::实现中文SayHi
  4.  SayHiEnglish: 实现英文SayHI
  5.  SayHiBoth:调用副AppDomain中的实现中英文SayHi
  6. SayHiException:在条用过程中抛出一个异常,并且检查副AppDomain中的状态。 

AppDomain的基本方法

        默认AppDomain就是在应用程序一开始运行所在的程序域。而副AppDomain指的就是在默认程序域或者其他程序域下创建的程序域。

  •  创建一个AppDomain。

             无论在程序域中或者副AppDoman中都可以创建新的AppDomain,而这个程序域就是受到当前程序域管理的。如:             

AppDomain chineseAppDomain =  AppDomain.CreateDomain( " chinese " ); 

        输入参数味AppDomain的名称,这个名称可以重复。也就是你可以有多个叫做chinese的的程序域
默认地 chineseAppDomain. BaseDirectory指向的目录是默认程序域的目录。当程序运行的时候,需要元数据,会后首先从这个目录开始查找。

  • 卸载一个程序域。

 

 AppDomain.Unload(aAppDomainInstance);

 
        默认程序域是不能被卸载的,否则回抛出CannotUnloadAppDomainException 错误。
       如果是副程序域自己卸载自己,哪么抛出内部抛出System.Threading.ThreadAbortException 异常,但是在外部可以捕获System.AppDomainUnloadedException。

  • 获取当前的AppDomain
AppDomain dom  =  AppDomain.CurrentDomain.

        如果当前在默认程序域中,哪么就获取默认程序域对象,如果在其他副AppDomain中,哪么就获取各自的AppDomain。

         创建第一个例子。在项目MultiAppDomain,打开文件Program.cs ,在Main 函数中找到

AppDomain chineseAppDomain  =  AppDomain.CreateDomain( " chinese " );
AppDomain englishAppDomain 
=  AppDomain.CreateDomain( " english " );
AppDomain bothAppDomain 
=  AppDomain.CreateDomain( " both " );
AppDomain exceptionDomain 
=  AppDomain.CreateDomain( " exception " );
  • AppDomain之间值传递

         只有继承了System.MarshalbyRefObject或者声明属性SerializableAttribute的类的实例,才能在AppDomain之间传递。即:

[Serializable]
Public 
class  A
{}
 
Public Class B:System.MarshalbyRefObject
{}

       假设 A的实例a 和 B的实例b,需要从AppDomain X传递到AppDomain Y中。哪么 a实例相当于对象拷贝。在 AppDomain Y中任何对 a的操作都不会影响倒 AppDomainX中的a。而b是通过跨域的透明代理递到,相当于值引用。因此 在 AppDomain Y中,任何对b的操作对于对 AppDomain X中的b产生影响。参考下图:

在AppDoain中,可以通过AppDomain.SetData 和 GetData方法进行存取操作 

AppDomain的动态导入

        要实现在默认AppDomain中实现对副AppDomain的导入控制,必须获取副AppDomain中的RemotLoader的实例如下图

 

       导入前,首先打开 MultiAppDomain项目中的RemoteLoader.cs 文件,查看一下Load方法。

public   class  RemoteLoader : MarshalByRefObject
{
        
private  Dictionary < string , Assembly >  assemblies  =   new  Dictionary < string , Assembly > ();
        
public   void  Load( string  fullPath)
        {
            
byte [] fsContent;
            
using (FileStream fs =  File.OpenRead(fullPath))
            {
                fsContent 
=   new   byte [fs.Length];
                fs.Read(fsContent, 
0 , fsContent.Length);                
            }
            Assembly assembly 
=  Assembly.Load(fsContent);        
            assemblies.Add(assembly.GetName().Name, assembly);
        }
}

        在Load方法内,使用byte导入Assembly,这样做可以防止Assembly文件独占。如果使用文件路径方式导入即Assembly.Load(filePath), 那么文件在程序运行的过程中是不能被更改的。


       在这里说个题外话, 在写这个例子的开始时,我是使用路径导入方式,开始一直都很顺利。当实现BothSayHi中SayHi的时候,却抛出转换异常的错误,说引用的透明代理(__TransparentProxy)对象不能被转换成为ISay接口类型。但是把代码粘贴在Main方法中就正常。后来试过N种方法,包括改为抽象类、类等都还是不行。正在百思不得其解,无意中把load导入改为byte导入就正常了。

        我个人认为,是因为在Main方法中,自身独占了Debug目录下的Interface.dll。而其他副AppDomain是直接获取Interface项目下的Dll。因此在Main方法中的调用是成功的,因为元数据的所属文件只有自己占用。但是在BothSayHi中的Interface.dll是和其他副AppDomain一起使用的。因此在BothSayHi中,在转换的时候,刚好需要获取Interface的元数据,而interface刚好被其他副AppDomain占用而无法读取元数据。因此获取不了元数据,只好抛出转换异常。这个只是我个人的想法,希望有高手指出问题的本质。

      好了言归正传 然后RemoteLoader需要在副AppDomain中创建,并在默认AppDomain引用。
       在项目MultiAppDomain,文件Program.cs ,在Main 函数中找到:

RemoteLoader chineseLoader  =
                (RemoteLoader) chineseAppDomain.CreateInstanceAndUnwrap(
" MultiAppDomain " " MultiAppDomain.RemoteLoader " );

也可以写成:

RemoteLoader chineseLoader  =
                (RemoteLoader) chineseAppDomain.CreateInstance (
" MultiAppDomain " " MultiAppDomain.RemoteLoader " ).Unwrap;

        如果使用第二种方法创建,那么返回的是System.Runtime.Remoting.ObjectHandle 对象。这个对象在Msdn的定义是:包装按值封送对象引用,从而使它们可以通过间接寻址返回。因此在默认AppDomain中并不需要它的相关元数据(因此无需导入相关Assembly),直到使用方法unwarrp. 默认AppDomain才开始导入相关的元数据。
        Unwrap后对象是一个透明代理,再进行强制转换。获得RemoteLoader代理后,为每个副域导入相关Assembly,如下:

internal   class  Program
{
 
string  chineseDll  =   @" ../../../SayHiChinese/bin/Debug/SayHiChinese.dll " ;
 
string  interfaceDll  =   @" ../../../Interface/bin/Debug/Interface.dll " ;
// 省略代码
private  static   void  Main( string [] args)
{
// 省略代码
chineseLoader.Load(interfaceDll);
chineseLoader.Load(chineseDll);
chineseLoader.CreateService(
" sayHi " " SayHiChinese " " SayHiChinese.SayHi " );
// 省略代码
}
}

        留意下chineseLoader.Load(interfaceDll),这句是可以省略的(开始不明原因,兴奋了好一阵子,以为.net有这么高的只能)。首先,在MultiAppDomain中所在的默认目录已经存在这个文件(因为项目显示引用了Interface项目),然后在创建chineseAppDomain的时候,我并没有指定副AppDomain的BaseDirectory,因此BaseDirecotry默认指向MultiAppDomain的运行目录。而在这MultiAppDomain项目中,我们引用了Interface,因此在路径中也包含了Interface.Dll,所以当我们调用chineseLoader.CreateService()创建SayHiChinese.SayHi实例时,CLR自动从MultiAppDomain目录中,自动导入Interface.Dll。

因为BothSayHi是需要其他两个实例的支持才能完成,因此创建代码就需要输入chinese和English的实例的

bothLoader.Load(interfaceDll);
bothLoader.Load(bothDll);
bothLoader.SetServiceFrom( " chinese " , chineseLoader.GetService( " sayHi " ));
bothLoader.SetServiceFrom(
" english " , englishLoader.GetService( " sayHi " ));
bothLoader.CreateService(
" sayHi " " BothSayHi " " BothSayHi.SayHi " );

在BothSayHi中实现调用chinese和english的服务:
bothSayHi的方法

public   string  Say()
{  
            ISayHi chinese
= (ISayHi)AppDomain.CurrentDomain.GetData( " chinese " );
            ISayHi english 
=  (ISayHi) AppDomain.CurrentDomain.GetData( " english " );
            Console.WriteLine(chinese.Say() 
+   "   "   +  english.Say());
            
return  chinese.Say()  +   "   "   +  english.Say();
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值