zoukankan      html  css  js  c++  java
  • 【.net 深呼吸】跨应用程序域执行程序集

    应用程序域,你在网上可以查到它的定义,凡是概念性的东西,大伙儿只需要会搜索就行,内容看了就罢,不用去记忆,更不用去背,“名词解释”是大学考试里面最无聊最没水平的题型。

    简单地说,应用程序域让你可以在一个进程中将某些代码隔离执行,相同的代码可以在不同的应用程序域中独立执行,互不干扰。也就是我做我的事,他干他的活,互不影响。

    一、隔离性

    先来看看,应用程序域之间的隔离是怎么一回事,请原谅老周的理论水平低下,从来不会长篇大论地叙述,老周最大的特长是写代码来说明问题。所以,关于应用程序域之间的隔离性,还是写代码来展示吧。

    咱们来写一个静态类。

        public static class Demo
        {
            public static string Title { get; set; } = "<NULL>";
            public static string Content { get; set; } = "<NULL>";
        }

    经验告诉我们,静态成员是基于类型的,它的调用不需要实例化,所以,静态成员的值一般都可以全局使用。大伙儿也应该知道,应用程序中至少会存在一个应用程序域,在应用程序运行时自动创建。

    下面我们来做一个神奇实验,这个实验的结果会让你意外的。

    先在应用程序自动创建的应用程序域中为静态类的静态成员赋值。

                // 在应用程序自动创建的应用程序域中赋值
                Demo.Title = "大甩卖";
                Demo.Content = "二手豆腐渣商品房,200元一平米。";

    好,然后,我们创建一个新的应用程序域,给它一个名字,叫no face吧。

                // 创建新的应用程序域
                AppDomain newDomain = AppDomain.CreateDomain("no_face");

    AppDomain有一个方法叫DoCallBack,方法的调用需要一个委托作为参数,委托关联的方法无参数,返回类型为void。这个方法的作用是在指定的应用程序域中执行委托所关联的代码。

    现在,我们在新创建的应用程序域中访问Demo静态类的成员,看看会输出什么。

                newDomain.DoCallBack(() =>
                {
                    string msg = $"【今日头条】
    
    标题:{Demo.Title}
    正文:{Demo.Content}";
                    Console.WriteLine(msg);
                });

    运行程序后,你会看到以下输出。

    这说明了啥?说明两个应用程序域之间是独立的,虽然静态类型好像是“全局”的,但它是相对于同一个应用程序域而言的。从刚刚的例子中看到,为静态成员赋值是在程序默认创建的应用程序域中执行的,而对于新创建的应用程序域中是隔离的,也就是说,赋值在新的应用程序域中是不存在的,所以输出的仍是默认值。

    二、执行带入口点的程序集

    AppDomain类公开了 ExecuteAssembly 和 ExecuteAssemblyByName 方法,没有带“Name”的方法是从程序集所在的文件中加载,而带有“Name”的方法是通过程序集名称来加载的,至于用哪个方法,你看着办吧。

    不过,调用这两个方法执行的程序集必须要有入口点的,所以不能是.dll,类库不能定义入口点。

    声明一个类,然后记得在类中定义Main方法。

        public class Class1
        {
            static int Main(string[] args)
            {
                Console.WriteLine("程序集正在{0}域中运行。", AppDomain.CurrentDomain.FriendlyName);
                if (args.Length > 0)
                {
                    Console.WriteLine("参数:{0}", string.Join("", args));
                }
                return 0;
            }
        }

    如果新建的项目是类库,可以打开项目属性,把项目类型改为 Windows应用程序。

    然后,选择一个入口点(带有Main方法的类型)。

    回到主项目,创建新的应用程序域,然后执行一个带入口点的程序集。

                string file = "..\..\..\TestAssem\Bin\Debug\TestAssem.exe";
                AppDomain newappd = AppDomain.CreateDomain("new-appd");
                int r = newappd.ExecuteAssembly(file, new string[] { "arg1", "arg2", "arg3" });
                Console.WriteLine("执行结果:{0}", r);

    ExecuteAssembly 方法的返回值,就是要执行程序集的Main方法的返回值。可以通过一个字符串数组来向要执行的程序集传递参数,参数会传到目标Main方法中。

    看看执行结果。

    三、跨域封送

    在一个应用程序域中执行代码,除了前面提到过 DoCallback 方法,还可以用 CreateInstanceXXXX 方法,这些方法是直接创建一个在指定应用程序域执行类型的实例,由于实例是跨应用程序域传送的,所以需要封送,被创建的实例会用一个ObjectHandle 封装,然后你要调用Unwrap方法来解封,才能得到对象实例。如果希望一次性完成这个过程,可以调用带有 AndUwrap的方法,这样在创建实例后会自动封送到当前应用程序域,并自动解封。

    使用CreateInstanceXXX方法,主要是可以把其他应用程序域中的变量传到当前的应用程序域,而 DoCallback 方法适用于可以独立在另一应用程序域执行的代码,当前应用程序域无需引用变量。

    假设,我定义这么个类。

    namespace OXX
    {
        public class Player
        {
            public void Play()
            {
                Console.WriteLine($"正在应用程序域“{AppDomain.CurrentDomain.FriendlyName}”上玩耍。");
            }
        }
    }

    然后,在新创建的应用程序域中执行它。

                AppDomain newdomain = AppDomain.CreateDomain("new_face");
    
                Type objtype = typeof(OXX.Player);
                OXX.Player obj = (OXX.Player)newdomain.CreateInstanceAndUnwrap(objtype.Assembly.FullName, objtype.FullName);
                obj.Play();

    代码比较简单,先创建应用程序域,然后创建类型实例,接着调用类型实例成员。但,这样一运行就会发生错误的,因为类型实例是要从一个应用程序域传送到另一个域,而每个域的代码是相互隔离的,因此,实例传递需要让类型可序列化,这样才能从一个应用程序域复制到另一个应用程序域。

    第一种方法,就是加上Serializable 特性。

        [Serializable]
        public class Player
        {
            public void Play()
            {
               
            }
        }

    这样声明后,对象实例会按值来传递,即实例会从 new_face 应用程序域复制一份到当前域,所以,当你运行应用程序后,你会发现,输出的应用程序域名称是默认的应用程序域。

    因为实例被复制了一份,而Play方法是在默认应用程序域中调用的,所以它输出的当前应用程序域名就是这个默认的域的名字,这与值类型的传递差不多,对象自身会被复制。

    如果你想让 obj 变量能在新的应用程序域中执行,可以采用第二种方法,就是不使用 Serializable 特性,改为从 MarshalByRefObject 类派生,这样就能实现引用传递,此时传送的是对象实例的引用,而不是复制一份。

        public class Player : MarshalByRefObject
        {
            public void Play()
            {
                ……
            }
        }

    现在,再次运行示例,发现输出的是新创建的应用程序域的名字,而不再是默认域的名字了。

    好了,今天的话题就讨论到此了。

    示例源代码下载

  • 相关阅读:
    vue 项目编译打包
    【Vue】基于nodejs的vue项目打包编译部署
    关于数据库设计中的状态字段
    Node.js安装及环境配置之Windows篇
    REST的本质,就是用户操作某个网络资源(具有独一无二的识别符URI),获得某种服务,也就是动词+资源(都是HTTP协议的一部分)
    微软重生:4年市值U型大逆转,超越谷歌重返巅峰!
    我在世界最热创业孵化器YC学到的58件事
    创业是否只是年轻人的专利?
    让你更值钱的方法:培养稀缺(追逐新技术,淬炼已有技能、做到出类拔萃,寻找自己所在的行业痛点,App开发者是市场动态平衡的典型)
    算法题
  • 原文地址:https://www.cnblogs.com/tcjiaan/p/5893430.html
Copyright © 2011-2022 走看看