跨AppDomain的对象访问
- AppDomain
Windows使用进程边界来隔离在同一台计算机上运行的应用程序。每一个应用程序被加载到单独的进程中,在目标进程中,不能通过任何有意义的方式使用从一个进程传递到另一个进程的内存指针,也不能在两个进程间进行直接调用,要使用代理间接调用。
CLR使用应用程序域(AppDomain)进行程序间的隔离,可以在单个进程中运行多个应用程序域,但隔离级别与进程相当,而且不会造成进程间调用或进程间切换等方面的额外开销。简单来讲,它可以提供与进程边界相当的隔离级别,而其性能开销则要低得多。
隔离应用程序对于应用程序安全十分重要。当一个应用程序中出现的错误不会影响其他应用程序。因为应用程序域可以确保在一个应用程序域中运行的代码不会影响进程的其他部分。另外还能够在不停止整个进程的情况下停止单个应用程序,卸载相应的应用程序域不会影响进程中其他应用程序域。
CLR不允许一个应用程序域中的变量引用另一个应用程序域中的对象,这样会破坏隔离,但使用额外的方法可以间接访问。有两种方法来实现:一是使用代理,二是使用副本。
- 按引用封送 按引用封送(MarshalByRef)。按引用封送的对象需要继承System.MarshalByRefObject。当调用方在它自己的应用程序域中创建按引用封送的对象实例时,CLR会在在调用方的应用程序域中创建该对象的代理,并向调用方返回对此代理的引用。当调用方在此代理上进行调用时,CLR将封送这些调用,将其发送回起始应用程序域,并引发对实际对象的调用。
-
需要注意的是,代理将与起始的应用程序域有相同的生命周期。当起始应用程序域被卸载,代理也将不能使用。
- 按值封送
按值封送(MarshalByRef)。按值封送的对象必须声明可序列化(通过[Serializable]特性或额外的Iserializable接口)。当调用方在它自己的应用程序域中创建按引用封送的对象实例时,CLR会通过序列化的方式,创建这些对象的完整副本并传递到调用方的应用程序域。调用方直接对该副本进行调用。
副本与起始应用程序域中的对象完全无关,生命周期也不同。当起始应用程序域被卸载,副本可以继续使用。
如果同时声明了按引用封送和按值封送,CLR会优先按引用封送对象。
- Demo
准备三个类,分别声明成按引用封送、按值封送、不封送,并且都实现一个方法CallMethod,显示方法执行时的应用程序域名称。
主程序创建三个独立的应用程序域,分别在这三个应用程序域中创建上述三个类的实例,并在当前应用程序域中返回三个实例的引用。我们利用这些引用访问相应的对象,调用其方法,以实现跨应用程序域的访问。
Demo代码
1 namespace Wuhong.AppDomainTest
2 {
3 /// <summary>
4 /// 按引用封送类
5 /// </summary>
6 public class MBR : MarshalByRefObject
7 {
8 public void CallMethod()
9 {
10 AppDomain currentDomain = AppDomain.CurrentDomain;
11 Console.WriteLine("MBR AppDomain:{0}",currentDomain.FriendlyName);
12 }
13 }
14
15 /// <summary>
16 /// 按值封送类
17 /// </summary>
18 [Serializable]
19 public class MBV
20 {
21 public void CallMethod()
22 {
23 AppDomain currentDomain = AppDomain.CurrentDomain;
24 Console.WriteLine("MBV AppDomain:{0}", currentDomain.FriendlyName);
25 }
26 }
27
28 /// <summary>
29 /// 不封送
30 /// </summary>
31 public class Non
32 {
33 public void CallMethod()
34 {
35 AppDomain currentDomain = AppDomain.CurrentDomain;
36 Console.WriteLine("Non AppDomain:{0}", currentDomain.FriendlyName);
37 }
38 }
39
40 class Program
41 {
42 static void Main(string[] args)
43 {
44 string assemblyName = Assembly.GetEntryAssembly().FullName;
45
46 //为三个对象创建独立的应用程序域
47 AppDomain appRef = AppDomain.CreateDomain("MarshalByRef Domain");
48 AppDomain appValue = AppDomain.CreateDomain("MarshalByValue Domain");
49 AppDomain appNon = AppDomain.CreateDomain("NonMarshal Domain");
50
51 //当前应用程序域
52 CallMethod();
53
54 try
55 {
56 Console.WriteLine("**************************MarshalByRef**************************");
57
58 //按引用封送
59 MBR mbr = (MBR)appRef.CreateInstanceAndUnwrap(assemblyName, typeof(MBR).FullName);
60 Console.WriteLine("MarshalByRefObject Created");
61
62 //透明代理
63 Console.WriteLine("Is TransparentProxy? {0}", RemotingServices.IsTransparentProxy(mbr));
64
65 //调用
66 mbr.CallMethod();
67
68 //卸载应用程序域
69 AppDomain.Unload(appRef);
70 Console.WriteLine("MarshalByRef Domain Unloaded");
71
72 //不能调用
73 mbr.CallMethod();
74 }
75 catch (Exception ex)
76 {
77 Console.WriteLine(ex.Message);
78 }
79
80 try
81 {
82 Console.WriteLine("**************************MarshalByValue**************************");
83
84 //按值封送
85 MBV mbv = (MBV)appValue.CreateInstanceAndUnwrap(assemblyName, typeof(MBV).FullName);
86 Console.WriteLine("MarshalByValueObject Created");
87
88 //不是透明代理
89 Console.WriteLine("Is TransparentProxy? {0}", RemotingServices.IsTransparentProxy(mbv));
90
91 //调用访问
92 mbv.CallMethod();
93
94 //卸载应用程序域
95 AppDomain.Unload(appValue);
96 Console.WriteLine("MarshalByValue Domain Unloaded");
97
98 //可以继续访问
99 mbv.CallMethod();
100 }
101 catch (Exception ex)
102 {
103 Console.WriteLine(ex.Message);
104 }
105
106 try
107 {
108 Console.WriteLine("**************************NonMarshal**************************");
109
110 //不封送,不能创建实例
111 Non nm = (Non)appNon.CreateInstanceAndUnwrap(assemblyName, typeof(Non).FullName);
112 nm.CallMethod();
113 AppDomain.Unload(appNon);
114 nm.CallMethod();
115 }
116 catch (Exception ex)
117 {
118 Console.WriteLine(ex.Message);
119 }
120
121 Console.ReadLine();
122 }
123
124 static void CallMethod()
125 {
126 AppDomain currentDomain = AppDomain.CurrentDomain;
127 Console.WriteLine("Current AppDomain:{0}", currentDomain.FriendlyName);
128 }
129 }
130 }
运行结果:
分析结果我们可以看到:
实例MBR实际上只是真实对象的透明代理,调用CallMethod方法时,会被封送回起始的应用程序域“MarshalByRef Domain”,方法实际是在这里执行的。当应用程序域“MarshalByRef Domain”被卸载后,MBV也失效了。
实例MBV不是透明代理,而是真实对象,CallMethod方法是在当前默认的应用程序域中执行,而应用程序域“MarshalByValue Domain”是否卸载,不影响MBV的使用。
实例Non未做任何可封送的声明,不能跨应用程序域访问。