应用程序域

来源:互联网 发布:和服木屐 知乎 编辑:程序博客网 时间:2024/05/02 10:28

一.应用程序域(AppDomain)的基本概念

     .net可执行程序集(.exe)在运行时都寄宿在操作系统进程中,同时程序集又需要运行在托管环境中,但操作系统进程只提供基本的服务,并不了解托管代码。因此就需要有一个中介,既能为程序集提供托管环境,又能运行在非托管的操作系统进程之内,这个中介就是应用程序域。简单来说,应用程序域只是允许它所加载的程序集访问由.net公共语言进行时所提供的服务。

      可以将应用程序域视为一个轻量级的进程,在一个操作系统进程中可以包换多个应用程序域。在可执行程序集加载完毕后,.net会在当前进程中创建一个新的应用程序域,称之为默认应用程序域。一个进程只能创建一个默认应用程序域,这个应用程序域的名称与程序集文件的名称相同。默认应用程序域不能被卸载,并且与其所在的进程同生共死。下图显示了进程,公共语言运行时和应用程序域之间的关系。

    

  

      从图可以看出,应用程序域提供了与进程类似的隔离性。当一个进程中包含多个应用程序域时,彼此之间是相互独立的,当某一个应用程序域出现致命错误导致程序崩溃时,不会影响其他的应用程序域,进程也不会结束。在进程中新建一个应用程序域的开销,要远小于操作系统新建一个进程开销。


二.应用程序域的基本操作

      在.net中,将应用程序域封装为了System.AppDomain类,这个类提供了和应用程序域有关的各种操作,包括创建应用程序域,为应用程序域加载程序集,创建对象等。在通常情况下编程,几乎不需要对AppDomain进行操作。这里仅仅介绍几个会用到的,有助于理解和调试Remoting的常见操作。

 1. 获取当前运行的代码所在的应用程序域,可以使用AppDomain类的静态属性CurrentDomain,或者使用Thread类的静态方法GetDomain(),得到当前线程所在的应用程序域:

AppDomain currentDomain=AppDomain.CurrentDomain();

AppDomain  currentDomain=Thread.GetDomain();


2.获取应用程序域的名称,使用AppDomain的实例只读属性---FriendlyName

string name=AppDomain.CurrentDomain.FriendlyName;


3.创建新应用程序域,可以使用CreateDomain()静态方法,并且传入一个字符串作为新应用程序域的名称(相当于设置了FriendlyName属性)

AppDomain =AppDomain.CreateDomain("NewDomain");


4.在应用程序域中创建对象,可以使用AppDomain的实例方法CreateInstanceAndUnWrap()或者CreateInstance()方法。此方法包含两个参数,第一个参数为类型所在的程序集名称,第二个参数为类型全称:

//使用CreateInstanceAndUnWrap()方法创建对象

DemoClass obj=(DemoClass)AppDomain.CurrentDomain.CreateInstanceAndUnWrap("ClassLib","ClassLib.DemoClass");

//使用CreateInstance()方法创建对象

ObjectHandle objHandle=AppDomain.CurrentDomain.CreateInstance("ClassLib",''ClassLib.DemoClass");

DemoClass obj=(DemoClass)objHandle.UnWrap();


5.判断是否是默认应用程序域

newDomain.IsDefaultAppDomain()


三.简单的列子

3.1 在默认应用程序域中创建对象

  先创建一个类库项目ClassDemo,然后在其中创建一个DemoClass,这个类的实例即为将要在默认应用程序域中创建的对象:

namespace ClassDemo
{  
   public class DemoClass
    {
       private int count = 0;
       public DemoClass()
       {
           Console.WriteLine("----DomoClass Constructor-----");
       }


       public void ShowCount(string name)
       {
           count++;
           Console.WriteLine("{0},the count is {1}.",name,count);
       }


       //打印对象所在的应用程序域
       public void ShowAppDomain()
       {
           AppDomain CurrentDomain = AppDomain.CurrentDomain;
           Console.WriteLine(CurrentDomain.FriendlyName);
         //  Console.ReadLine();
       }
    }
}


接下来,再创建一个控制台应用程序,将项目命名为ConSoleApp,引入上面创建的类库项目ClassDemo,然后代码如下:

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Test1();
        }


        //当前AppDomain中创建一个对象
         static void Test1()
        {   
            AppDomain currentDomain = AppDomain.CurrentDomain;
            Console.WriteLine(currentDomain.FriendlyName);
            DemoClass obj;
            //默认应用程序域中创建对象。
            obj = (DemoClass)currentDomain.CreateInstanceAndUnwrap("ClassDemo", "ClassDemo.DemoClass");
            obj.ShowAppDomain();
            obj.ShowCount("JImmy");
            obj.ShowCount("JImmy");
            Console.ReadLine();
        }
    }
}

运行后的结果为:

ConsoleApp.exe

-------DomoClass Constructor---------

ConsoleApp.exe

Jimmy,the count is 1

jimmy,the count is 2


上面的代码在当前的默认应用程序域中创建了一个DemoClass的实例,并调用了它的方法。


3.2 在新建应用程序域中创建对象

有些读者看到这可能会想,这还不简单,在写一个方法Test2()方法,在里面新建一个AppDomain即可,其余代码不变。

也就是这个样子

static void Test2()
        {   
            AppDomain currentDomain = AppDomain.CurrentDomain;
            Console.WriteLine(currentDomain.FriendlyName);
            //创建一个新的应用程序域--NewDomain
            AppDomain newDomain = AppDomain.CreateDomain("NewDomain");
            DemoClass obj;
            //在新的应用程序域中创建对象
            obj = (DemoClass)newDomain.CreateInstanceAndUnwrap("ClassDemo", "ClassDemo.DemoClass");
            obj.ShowAppDomain();
            obj.ShowCount("JImmy");
            obj.ShowCount("JImmy");
            Console.ReadLine();
        }

但是最终的结果会报错,说ClassDemo.DemoClass未被标记为可序列化。

为什么会出现这个问题呢,看一下声明obj变量的代码:DemoClass obj,这说明obj是在当前的默认应用程序域,也就是AppConsole.exe中声明的,再往下看,类型的实例(对象本身)却是通过)newDomain.CreateInstanceAndUnwrap()在新创建的应用程序域--NewDomain中创建的。这样就出现了不一致的情况:

    对象的引用,变量obj位于当前的默认应用程序域ConsoleApp.exe中,而对象本身位于新创建的应用程序域NewDomain中,前面提到过,默认情况下AppDomain是独立的,不能直接在一个应用程序中引用另一个应用程序域中的对象,所以这里出现异常。

   加了序列化[Serializable]后,在运行就能正常运行。


注意:

    DemoClass obj=new DemoClass();  //方式一

    DemoClass obj=(DemoClass)newDomain.CreateInstanceAndUnwrap("ClassDemo", "ClassDemo.DemoClass")  //方式二

当使用第一种方式时,在托管堆中创建了一个对象,并且由obj变量直接引用了这个对象;采用第二种方式时,实际上创建了两个对象;先在NewDomain中创建了这个对象,然后将对象的状态进行复制,序列化,之后进行封送,接着在ConsoleApp.exe(客户端应用程序域)重新创新这个对象,还原对象状态,创建对象代理(Proxy)。最后通过这个代理来访问这个对象,此时,因为代理访问的是在本地重新创建的对象而非远程对象,所以当在代理上调用ShowDomain()时,显示的是ConsoleApp.exe。

     上面的说明中出现了两个新名词,代理和封送。现在先来解释一下代理,代理提供了和远程对象(本例中是在newDomain中创建的DemoClass)完全相同的接口(属性,方法,事件)。.Net需要在客户端(本例中是ConsoleApp.exe)基于远程对象的类型元数据(Type Metadata)来创建代理,因此客户端必须包含远程对象的类型元数据。元数据简单来说就是类型的接口:类型名称,公共属性名称,方法的名称和签名,但是没有实现。

       因为代理有着和远程对象完全一样的接口和名称,所以对客户程序来说,代理就好像远程对象一样。但代理实际上又并不包含向客户程序提供服务的实际代码。比如方法体,所以代理仅仅是将自己与某一实际对象绑定,然后把客户程序对自己的请求打包成消息(Message),随后发送给实际对象。将请求发送给实际对象的过程,叫做封送(Marshal)。使用代理的好处就是,对于客户端程序来说,远程的服务端对象就好像是在本地一样,而对远程对象来说,也好像是为其本地对象提供服务。


四.传值封送,传引用封送

  

AppDomain之间对象传递有两种方式,以下分别介绍:

4.1 按值封送方式

      把一个类标识上可以序列化,即为类型添加SerializableAttribute即标志着类型实例化出来的对象可以按值封送传递到另一应用程序域中,其内部原理就是在原AppDomain中把类序列化成字节流,传递到另一AppDomain中,在目标AppDomain中再实例化出一个原对象的副本然后用传递过来的字节流对副本进行填充,这样目标对象看起来跟原对象是一样的,但是是一个副本,对副本的任何修改并不反应到原对象上。

4.2 按引用封送方式

   所谓引用封送就是将对象传递到另一AppDomain后对对象所做的修改照实的反应到原对象上,而不是创建副本,对副本进行修改。

0 0