《CLR via C#》读书笔记-CLR寄宿与AppDomain

时间:2022-12-28 17:30:24

1、CLR寄宿

        在Window平台上程序之间的调用分为两类:托管程序调用非托管程序和非托管程序调用托管程序。前者通常使用P/Invoke方式调用,用的最多的就是Win32 编程接口了。举个例子:
using System.Runtime.InteropServices;
......

[DllImport("kernel32.dll")]
public static extern int WinExec(string exeName, int operType);

WinExec(@"C:\**\ctsetdg.exe", 5);

        优点:很多功能都已经实现,无需重复开发。缺点:1、不受控制。2、API接口好多。这儿有一个API的文档说明。具体网址在这儿: API的说明文档

        但是当非托管程序调用托管程序时,就需要用到CLR寄宿了。我们知道,一个托管应用程序首先被操作系统启动,然后由操作系统调用CLR来托管该程序。CLR的本质是一个被“包装”到DLL的一个COM服务器。因此非托管程序可根据接口调用CLR来运行托管程序。我们把这种方式称之为CLR寄宿。把非托管程序成为宿主。
        宿主程序“拥有”CLR之后,就可以使用CLR的功能进行内存管理、垃圾回收、策略管理、事件管理等

        CLR通过XX头文件公布了如何调用CLR的接口内容。里面方法太多,没细看(哈哈,实际是本人看不懂),这儿有一篇博文,详细介绍了CLR的寄宿,推荐: 网址在这儿。虽然不懂,但可以根据《CLR via C#》先整理出大体的框架。具体如下:
       
        第一步:创建COM服务器。创建服务器有两种方法:1、通过CoCreateInstance;2、通过CLRCreateInstance。《CLR via C#》的作者建议使用后者。
        第二步:CLRCreateInstance返回ICLRMetaHost接口。通过这个接口的GetRuntime函数,可以返回ICLRRuntimeInfo的指针。
        第三步:通过第2步得到的指针,可以其GetInterface方法,获取ICLRRuntimeHost接口。
        第四步:可以通过第三步得到的接口,完成调用程序集,并执行其中的代码。(其实,通过这个接口还可以完成:设置宿主管理器、获取CLR管理器、初始化并加载具体的CLR、停止CLR等)。

        说一下个人理解:
        1、CLR寄宿的主要功能是为编写非托管代码的程序员提供一种执行托管代码的方法。因此,C#程序员可以不用过多的注意这个话题,若需要做需要与外部对接时,再研究也不迟。
        2、在CLR寄宿的本质,是反射。创建CLR服务器、加载需要执行的托管代码、通过反射调用其中的相关方法。这是CLR寄宿的基本思路。在这个过程中又有几个小概念:
             2.1、MSCorEE.DLL。这个DLL被称之为“shim”垫片。其相当于《黑客帝国》中保护先知(Oracle)的保镖。不管先知的容貌如何改变,这个保镖总能找到并保护先知。这个DLL也是这个作用。它可以根据用户的需要,找到执行的CLR的版本。
            2.2、GUID。GUID是一个所有类、接口、类型的全世界唯一ID。其128位长度,通过算法保证。而COM服务器的创建,会在注册表中注册GUID,因此程序只需要通过GUID就可以找到DLL,而无需担心DLL的名称、具体位置等信息。因此,可以通过更新注册表中的相关内容,就可以神不知鬼不觉的完成了程序更新。

        以上就是CLR寄宿的章节的读后感。
        另外,在查找资料过程中,有几篇技术博客不错,在此推荐:1、介绍MSCorEE.DLL的博客: MSCorEE的功能。2、介绍CoCreateInstance的博文: CoCreateInstance

2、APPDomain

        当非托管程序通过CLR寄宿运行托管程序时,有两种方式:1、不加任何限制的运行托管程序;2、为托管程序单独创建一个“沙箱”,只允许托管程序在“沙箱”内运行。两种方式相比,后者更安全。另外,当程序本身为托管代码程序时(例如:C#编写的程序),调用第三者的程序作为扩展程序,为保证安全,同样可以采用刚才所提的“沙箱”的方式。为此,.NET中提供了AppDomain,其唯一的作用隔离。
        另外,当一个非托管程序调用托管程序的操作逻辑如下:
                                 《CLR via C#》读书笔记-CLR寄宿与AppDomain
        在非托管程序与托管程序之间,需要写一个托管代码,用于调用托管程序。就如上图中“漏斗”的作用。在《CLR via C#》中作者提供了一个例子:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Threading;

public sealed class Program {
   public static void Main() {
      Marshalling();
      //FieldAccessTiming();
      //AppDomainResourceMonitoring();
      //UnloadTimeout.Go();
   }

   private static void Marshalling() {
      //获取默认的AppDomain
      AppDomain adCallingThreadDomain = Thread.GetDomain();

      //展示默认名称
      String callingDomainName = adCallingThreadDomain.FriendlyName;
      Console.WriteLine("Default AppDomain's friendly name={0}", callingDomainName);

      //展示程序集名称
      String exeAssembly = Assembly.GetEntryAssembly().FullName;
      Console.WriteLine("Main assembly={0}", exeAssembly);

      // Define a local variable that can refer to an AppDomain
      AppDomain ad2 = null;

      // *** DEMO 1: 通过按引用传递的AppDomain
      Console.WriteLine("{0}Demo #1", Environment.NewLine);

	  //创建一个新的AppDomain,第一个参数代表名称,第二个参数代表AppDomain的安全决策设定,第三个参数为AppDomain的基本设定。为null,
          //代表与当前默认的AppDomain相同
      ad2 = AppDomain.CreateDomain("AD #2", null, null);
      MarshalByRefType mbrt = null;

	  //装载程序集,并创建指定的实例,然后返回
	  //此处mbrt本质是一个代理
      mbrt = (MarshalByRefType)ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");
		
	  //这儿的输出将是:MarshalByRefType,
      Console.WriteLine("Type={0}", mbrt.GetType());  

      //但通过下面的这句话,可以得到,mbrt是一个代理
      Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbrt));

      //表面上,我们调用了在新AppDomain上mbrt实例的方法。
	  //但实际上,线程会跳到SomeMethod“真正”定义的地方,并进行执行
      mbrt.SomeMethod();

      //卸载
      AppDomain.Unload(ad2);
      
	  //AppDomain卸载后,代理对象mbrt仍然存在,但是AppDomain将不复存在
	  //因此,下面的代码再次调用SomeMethod方法时,将会抛出异常
      try {
         mbrt.SomeMethod();
         Console.WriteLine("Successful call.");
      }
      catch (AppDomainUnloadedException) {
         Console.WriteLine("Failed call.");
      }


      // *** DEMO 2: 按值传递
      Console.WriteLine("{0}Demo #2", Environment.NewLine);

      // Create new AppDomain (security & configuration match current AppDomain)
      ad2 = AppDomain.CreateDomain("AD #2", null, null);

      // Load our assembly into the new AppDomain, construct an object, marshal 
      // it back to our AD (we really get a reference to a proxy)
      mbrt = (MarshalByRefType)
         ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

      // The object's method returns a COPY of the returned object; 
      // the object is marshaled by value (not be reference).
      MarshalByValType mbvt = mbrt.MethodWithReturn();

      // Prove that we did NOT get a reference to a proxy object
      Console.WriteLine("Is proxy={0}", RemotingServices.IsTransparentProxy(mbvt));

      // This looks like we're calling a method on MarshalByValType and we are.
      Console.WriteLine("Returned object created " + mbvt.ToString());

      // Unload the new AppDomain
      AppDomain.Unload(ad2);
      // mbvt refers to valid object; unloading the AppDomain has no impact.

      try {
         // We're calling a method on an object; no exception is thrown
         Console.WriteLine("Returned object created " + mbvt.ToString());
         Console.WriteLine("Successful call.");
      }
      catch (AppDomainUnloadedException) {
         Console.WriteLine("Failed call.");
      }


      // DEMO 3: Cross-AppDomain Communication using non-marshalable type
      Console.WriteLine("{0}Demo #3", Environment.NewLine);

      // Create new AppDomain (security & configuration match current AppDomain)
      ad2 = AppDomain.CreateDomain("AD #2", null, null);

      // Load our assembly into the new AppDomain, construct an object, marshal 
      // it back to our AD (we really get a reference to a proxy)
      mbrt = (MarshalByRefType)
         ad2.CreateInstanceAndUnwrap(exeAssembly, "MarshalByRefType");

      // The object's method returns an non-marshalable object; exception
      NonMarshalableType nmt = mbrt.MethodArgAndReturn(callingDomainName);
      // We won't get here...
   }
}

// Instances can be marshaled-by-reference across AppDomain boundaries
public sealed class MarshalByRefType : MarshalByRefObject
{
    public MarshalByRefType()
    {
        Console.WriteLine("{0} ctor running in {1}",
           this.GetType().ToString(), Thread.GetDomain().FriendlyName);
    }

    public void SomeMethod()
    {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
    }

    public MarshalByValType MethodWithReturn()
    {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
        MarshalByValType t = new MarshalByValType();
        return t;
    }

    public NonMarshalableType MethodArgAndReturn(String callingDomainName)
    {
        // NOTE: callingDomainName is [Serializable]
        Console.WriteLine("Calling from '{0}' to '{1}'.",
           callingDomainName, Thread.GetDomain().FriendlyName);
        NonMarshalableType t = new NonMarshalableType();
        return t;
    }

    [DebuggerStepThrough]
    public override Object InitializeLifetimeService()
    {
        return null;   // We want an infinite lifetime
    }
}


// Instances can be marshaled-by-value across AppDomain boundaries
[Serializable]
public sealed class MarshalByValType : Object
{
    private DateTime m_creationTime = DateTime.Now; // NOTE: DateTime is [Serializable]

    public MarshalByValType()
    {
        Console.WriteLine("{0} ctor running in {1}, Created on {2:D}",
           this.GetType().ToString(),
           Thread.GetDomain().FriendlyName,
           m_creationTime);
    }

    public override String ToString()
    {
        return m_creationTime.ToLongDateString();
    }
}

// Instances cannot be marshaled across AppDomain boundaries
// [Serializable]
public sealed class NonMarshalableType : Object
{
    public NonMarshalableType()
    {
        Console.WriteLine("Executing in " + Thread.GetDomain().FriendlyName);
    }
}



        通过上面的例子,可以得知:
        要想通过一个对象可以在不同的AppDomain之间传递,方法有两种:1、传递一个本地实例的“副本”,就是上面所提到的代理( 你实际上只拥有对这个对象的一个远程引用,虽然你可以调用它的方法,但实际上这些操作都是发生在远程的)。2、复制一个完全的版本,并传递过去。就是后面的例子。Demo1就是第一种情况,Demo2就是第二种情况。