13.ILR的类继承Unity中的类的限制

  1. 13.更多跨域调用-跨域继承Unity中的类的注意事项
    1. 13.1 知识点
      1. 复制之前的主入口调用
      2. 复习跨域继承的步骤
        1. 在Unity工程中实现基类
        2. 在ILRuntime工程中继承基类
        3. 通过工具生成跨域继承适配器
        4. 在初始化时,注册跨域继承适配器对象
        5. 这样我们就能在热更工程中正常跨域继承了
      3. 跨域继承中的注意事项
        1. 跨域继承中的注意事项概述
        2. 在Unity写一个基类和接口,在热更工程写一个类继承基类和接口,并在ILRuntimeMain实例化调用方法。
        3. 在GenerateCrossbindAdapter绑定并生成适配器
        4. 在ILRuntimeManager注册适配器类
        5. 运行会有报错,提示不能多继承。NotSupportedException: Inheriting and implementing interface at the same time is not supported yet。
        6. 因为本质上ILruntime的类继承的是适配器类,接口生成的适配器类也是类, public class Lesson13_InterfaceAdapter : CrossBindingAdaptor。但是C#不支持多继承类,所以报错了。所以ILruntime是不支持直接在ILruntime工程多继承的
        7. 如果一定要多继承可以在Unity实现一个多继承基类,再在ILruntime工程单继承。实现间接多继承。再绑定生成适配器并注册。重新生成后,可以正常使用间接多继承对象了。
        8. 注意,跨域继承中,不能在基类的构造函数中调用该类的虚函数,否则报错
      4. 总结
    2. 13.2 知识点代码
      1. Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项
      2. Lesson13_Father
      3. Lesson13_Interface
      4. Lesson13_InheritAll
      5. Lesson13_InheritAllSon
      6. ILRuntimeMain
      7. GenerateCrossbindAdapter
      8. ILRuntimeManager
    3. 13.3 练习题
      1. 如果想在ILRuntime跨域继承时多继承我们应该怎么做?

13.更多跨域调用-跨域继承Unity中的类的注意事项


13.1 知识点

复制之前的主入口调用

ILRuntimeManager.Instance.StartILRuntime(() =>
{
    //执行ILRuntime当中的一个静态函数
    //静态函数中 就是热更工程自己处理的逻辑了
    ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});

复习跨域继承的步骤

在Unity工程中实现基类

在ILRuntime工程中继承基类

通过工具生成跨域继承适配器

ILRuntime\Assets\Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCrossBinding.cs
按照其中的模板,填写自己要为哪个类生成跨域继承适配器对象

在初始化时,注册跨域继承适配器对象

appDomain.RegisterCrossBindingAdaptor(new 适配器类名());

这样我们就能在热更工程中正常跨域继承了

跨域继承中的注意事项

跨域继承中的注意事项概述

跨域继承时,尽量简单,不要存在多继承情况(比如类+多接口)

跨域继承基本原理:
ILRuntime中的跨域继承实际上并不是直接继承Unity中的基类
而是继承的适配器类
基类(Unity中)
|
适配器类(Unity工程中的实际类型)
|
子类(ILRuntime中)

注意事项:

  1. 跨域继承时,不支持多继承,即同时继承类和接口
  2. 如果项目框架设计中一定要出现多继承,那么在跨域继承时可以在主工程中声明一个多继承的基类用于跨域继承
  3. 跨域继承中,不能在基类的构造函数中调用该类的虚函数

在Unity写一个基类和接口,在热更工程写一个类继承基类和接口,并在ILRuntimeMain实例化调用方法。

public interface Lesson13_Interface
{
    public void TestInterface();
}
public abstract class Lesson13_Father
{
    public abstract void TestAbstract(int i);
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HotFix_Project
{
    class Lesson13_Son : Lesson13_Father, Lesson13_Interface
    {
        public override void TestAbstract(int i)
        {
            Debug.Log("TestAbstract:" + i);
        }

        public void TestInterface()
        {
            Debug.Log("TestInterface");
        }
    }
}
Lesson13_Son lesson13_Son = new Lesson13_Son();
lesson13_Son.TestAbstract(666);
lesson13_Son.TestInterface();

在GenerateCrossbindAdapter绑定并生成适配器

using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_FatherAdapter.cs"))
{
    sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_Father), "Lesson13_FatherNameSpace"));
}
        
using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_InterfaceAdapter.cs"))
{
    sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_Interface), "Lesson13_InterfaceNameSpace"));
}

在ILRuntimeManager注册适配器类

appDomain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
appDomain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));

运行会有报错,提示不能多继承。NotSupportedException: Inheriting and implementing interface at the same time is not supported yet。

因为本质上ILruntime的类继承的是适配器类,接口生成的适配器类也是类, public class Lesson13_InterfaceAdapter : CrossBindingAdaptor。但是C#不支持多继承类,所以报错了。所以ILruntime是不支持直接在ILruntime工程多继承的

如果一定要多继承可以在Unity实现一个多继承基类,再在ILruntime工程单继承。实现间接多继承。再绑定生成适配器并注册。重新生成后,可以正常使用间接多继承对象了。

public abstract class Lesson13_InheritAll : Lesson13_Father, Lesson13_Interface
{
    public abstract void TestInterface();
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HotFix_Project
{
    class Lesson13_InheritAllSon : Lesson13_InheritAll
    {
        public override void TestAbstract(int i)
        {
            Debug.Log("TestAbstract:" + i);
        }


        public override void TestInterface()
        {
            Debug.Log("TestInterface");
        }
    }
}
Lesson13_InheritAllSon lesson13_InheritAll = new Lesson13_InheritAllSon();
lesson13_InheritAll.TestAbstract(666);
lesson13_InheritAll.TestInterface();
using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_InheritAllAdapter.cs"))
{
    sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_InheritAll), "Lesson13_InheritAllNameSpace"));
}
appDomain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));

注意,跨域继承中,不能在基类的构造函数中调用该类的虚函数,否则报错

public abstract class Lesson13_InheritAll : Lesson13_Father, Lesson13_Interface
{
    public Lesson13_InheritAll()
    {
        //跨域继承中,不能在基类的构造函数中调用该类的虚函数
        //NullReferenceException: Object reference not set to an instance of an object
        TestInterface();
    }
    public abstract void TestInterface();
}

总结

跨域继承时(ILRuntime工程继承Unity主工程中的类)

  1. ILRuntime不支持跨域(ILRuntime继承Unity)多继承
  2. 如果一定要跨域(ILRuntime继承Unity)多继承,就在主工程中用一个类将多继承关系疏离继承好,在热更工程中继承该类
  3. 跨域继承中,不能在基类的构造函数中调用该类的虚函数,会报错

13.2 知识点代码

Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项

using System.Collections;
using System.Collections.Generic;
using BaseFramework;
using UnityEngine;

public class Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项 : MonoBehaviour
{
    void Start()
    {
        #region 复制之前的主入口调用

        ILRuntimeManager.Instance.StartILRuntime(() =>
        {
            //执行ILRuntime当中的一个静态函数
            //静态函数中 就是热更工程自己处理的逻辑了
            ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
        });

        #endregion

        #region 知识点一 复习跨域继承的步骤

        //1.在Untiy工程中实现基类
        //2.在ILRuntime工程中继承基类
        //3.通过工具生成跨域继承适配器
        //  ILRuntime\Assets\Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCrossBinding.cs
        //  按照其中的模板,填写自己要为哪个类生成跨域继承适配器对象
        //4.在初始化时,注册跨域继承适配器对象
        //  appDomain.RegisterCrossBindingAdaptor(new 适配器类名());

        //这样我们就能在热更工程中正常跨域继承了

        #endregion

        #region 知识点二 跨域继承中的注意事项

        //跨域继承基本原理:
        //ILRuntime中的跨域继承实际上并不是直接继承Unity中的基类
        //而是继承的适配器类
        //            基类(Unity中)
        //             |
        //          适配器类(Unity工程中的实际类型)
        //             |
        //            子类(ILRuntime中)

        //注意事项:
        //1.跨域继承时,不支持多继承,即同时继承类和接口

        //2.如果项目框架设计中一定要出现多继承
        //  那么在跨域继承时可以在主工程中声明一个多继承的基类用于跨域继承

        //3.跨域继承中,不能在基类的构造函数中调用该类的虚函数

        #endregion

        #region 总结

        //跨域继承时(ILRuntime工程继承Unity主工程中的类)
        //1.ILRuntime不支持跨域(ILRuntime继承Unity)多继承
        //2.如果一定要跨域(ILRuntime继承Unity)多继承,就在主工程中用一个类将多继承关系疏离继承好,在热更工程中继承该类
        //3.跨域继承中,不能在基类的构造函数中调用该类的虚函数,会报错

        #endregion
    }
}

Lesson13_Father

public abstract class Lesson13_Father
{
    public abstract void TestAbstract(int i);
}

Lesson13_Interface

public interface Lesson13_Interface
{
    public void TestInterface();
}

Lesson13_InheritAll

public abstract class Lesson13_InheritAll : Lesson13_Father, Lesson13_Interface
{
    public Lesson13_InheritAll()
    {
        //跨域继承中,不能在基类的构造函数中调用该类的虚函数
        //NullReferenceException: Object reference not set to an instance of an object
        // TestInterface();
    }
    public abstract void TestInterface();
}

Lesson13_InheritAllSon

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HotFix_Project
{
    class Lesson13_InheritAllSon : Lesson13_InheritAll
    {
        public override void TestAbstract(int i)
        {
            Debug.Log("TestAbstract:" + i);
        }


        public override void TestInterface()
        {
            Debug.Log("TestInterface");
        }
    }
}

ILRuntimeMain

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HotFix_Project
{

    #region 委托调用

    public delegate void MyILRuntimeDel1();
    public delegate int MyILRuntimeDel2(int i, int j);

    #endregion


    class ILRuntimeMain
    {
        /// <summary>
        /// 把逻辑处理权交给热更工程
        /// 它是一个启动函数
        /// </summary>
        public static void Start()
        {
            #region ILRuntime调用Unity

            //使用ILRuntime创建的空物体
            GameObject obj = new GameObject("ILRuntime创建的空物体");
            obj.transform.position = new Vector3(10, 10, 10);
            //Debug.Log(obj.transform.position);//(10.00, 10.00, 10.00)

            //添加物理的dll后 可以正常使用Rigidbody了
            Rigidbody rigidBody = obj.AddComponent<Rigidbody>();
            rigidBody.mass = 9999;

            // 可以直接添加Unity自己写的脚本
            obj.AddComponent<Lesson10_Test>();

            #endregion



            #region 委托调用



            ////在ILRuntime中 申明 Unity中自定义委托变量 装载 ILRuntime的函数 正常使用
            //MyUnityDel1 fun = Fun1;
            //fun();//IL_Fun1

            //MyUnityDel2 fun2 = Fun2;
            //int result = fun2(5, 6);//IL_Fun2
            //Debug.Log(result);//11




            ////在Unity中 申明 Unity中自定义委托变量 关联ILRuntime工程中的函数

            ////找到我们在Unity的脚本
            //Lesson11_更多跨域调用_委托调用 lesson11 = GameObject.FindObjectOfType<Lesson11_更多跨域调用_委托调用>();

            ////直接设置Unity中自定义委托变量为ILRuntime工程中的函数会报错
            ////报错信息:KeyNotFoundException: Cannot find convertor for global::MyUnityDel1

            ////使用自定义委托
            ////注册MyUnityDel1和MyUnityDel2的委托和委托转换器后才能正常执行
            //lesson11.fun = Fun1;
            //lesson11.fun();//IL_Fun1
            //lesson11.fun2 = Fun2;
            //result = lesson11.fun2(7, 7);//IL_Fun2
            //Debug.Log(result);//14




            ////使用Action或者Func
            ////因为使用的是Action或者Func 不需要注册委托转换器
            //lesson11.funAction = Fun1;
            //lesson11.funAction();//IL_Fun1
            //lesson11.funFunc = Fun2;
            //result = lesson11.funFunc(8, 8);//IL_Fun2
            //Debug.Log(result);//16



            ////在ILRuntime中自定义委托后使用 直接使用
            //MyILRuntimeDel1 funIL = Fun1;
            //funIL();//IL_Fun1
            //MyILRuntimeDel2 funIL2 = Fun2;
            //result = fun2(1, 1);//IL_Fun2
            //Debug.Log(result);//2

            #endregion


            #region 跨域继承Unity中的类

            //Lesson12_Son lesson12_Son = new Lesson12_Son();
            //lesson12_Son.TestFun("哈哈哈");
            //lesson12_Son.TestAbstract(99);
            //lesson12_Son.valuePublic = 100;

            #endregion

            #region 跨域继承Unity中的类的注意事项


            //Lesson13_Son lesson13_Son = new Lesson13_Son();
            //lesson13_Son.TestAbstract(666);
            //lesson13_Son.TestInterface();

            Lesson13_InheritAllSon lesson13_InheritAll = new Lesson13_InheritAllSon();
            lesson13_InheritAll.TestAbstract(666);
            lesson13_InheritAll.TestInterface();

            #endregion
        }


        #region 委托调用

        public static void Fun1()
        {
            Debug.Log("IL_Fun1");
        }

        public static int Fun2(int a, int b)
        {
            Debug.Log("IL_Fun2");
            return a + b;
        }

        #endregion

    }
}

GenerateCrossbindAdapter

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System;
using System.Text;
using System.Collections.Generic;
[System.Reflection.Obfuscation(Exclude = true)]
public class ILRuntimeCrossBinding
{
    [MenuItem("ILRuntime/生成跨域继承适配器")]
    static void GenerateCrossbindAdapter()
    {
        //由于跨域继承特殊性太多,自动生成无法实现完全无副作用生成,所以这里提供的代码自动生成主要是给大家生成个初始模版,简化大家的工作
        //大多数情况直接使用自动生成的模版即可,如果遇到问题可以手动去修改生成后的文件,因此这里需要大家自行处理是否覆盖的问题

        using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/Samples/ILRuntime/2.1.0/Demo/Scripts/Examples/04_Inheritance/InheritanceAdapter.cs"))
        {
            sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(TestClassBase), "ILRuntimeDemo"));
        }
        
        using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson12_更多跨域调用_跨域继承Unity中的类/Lesson12_FatherAdapter.cs"))
        {
            sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson12_Father), "Lesson12_FatherNameSpace"));
        }
        

        using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_FatherAdapter.cs"))
        {
            sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_Father), "Lesson13_FatherNameSpace"));
        }
        
        using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_InterfaceAdapter.cs"))
        {
            sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_Interface), "Lesson13_InterfaceNameSpace"));
        }
        
        using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/ILRuntime教程/Lesson13_更多跨域调用_跨域继承Unity中的类的注意事项/Lesson13_InheritAllAdapter.cs"))
        {
            sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson13_InheritAll), "Lesson13_InheritAllNameSpace"));
        }
        
        AssetDatabase.Refresh();
    }
}
#endif

ILRuntimeManager

using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Lesson12_FatherNameSpace;
using Lesson13_FatherNameSpace;
using Lesson13_InheritAllNameSpace;
using Lesson13_InterfaceNameSpace;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

namespace BaseFramework
{
    public class ILRuntimeManager : BaseSingletonInMonoBehaviour<ILRuntimeManager>
    {
        public AppDomain appDomain;

        //用于存储加载出来的 两个文件的内存流对象
        private MemoryStream dllStream;
        private MemoryStream pdbStream;

        private bool isStart = false;


        /// <summary>
        /// 用于启动ILRuntime初始化方法
        /// </summary>
        public void StartILRuntime(UnityAction callBack = null)
        {
            if (!isStart)
            {
                isStart = true;
                appDomain = new AppDomain();
                StartCoroutine(LoadHotUpdateInfo(callBack));
            }
        }

        public void StopILRuntime()
        {
            //1.释放流
            if (dllStream != null)
                dllStream.Close();
            if (pdbStream != null)
                pdbStream.Close();
            dllStream = null;
            pdbStream = null;
            //2.清空appDomain
            appDomain = null;

            isStart = false;
        }

        /// <summary>
        /// 初始化ILRuntime相关的内容
        /// </summary>
        private void InitILRuntime()
        {
            //其他初始化
            
            //无参无返回值不需要委托注册
            //MyUnityDel1因为转成的是无参无返回值的Action,Action在热更新里可以直接使用,所以不需要进行委托的注册
            //委托转换器注册(要把自定义委托无参无返回值MyUnityDel1委托转换为Action,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel1>((act) =>
            {
                return new MyUnityDel1(() =>
                {
                    ((System.Action)act)();
                });
            });

            //有参有返回值委托注册 因为是自己定的规则要自己手动进行委托注册
            appDomain.DelegateManager.RegisterFunctionDelegate<System.Int32, System.Int32, System.Int32>();
            
            //委托转换器注册(要把自定义委托有参有返回值MyUnityDel2委托转换为Func,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel2>((act) =>
            {
                return new MyUnityDel2((i, j) =>
                {
                    return ((System.Func<System.Int32, System.Int32, System.Int32>)act)(i, j);
                });
            });

            
            //注册跨域继承的父类适配器
            appDomain.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
            
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));
            
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));
            
            //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
            appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
        }

        /// <summary>
        /// 启动完毕并且初始化完毕后 想要执行的热更新的逻辑
        /// </summary>
        private void ILRuntimeLoadOverDoSomthing()
        {
        }

        /// <summary>
        /// 去异步加载我们的热更相关的dll和pdb文件
        /// </summary>
        /// <returns></returns>
        IEnumerator LoadHotUpdateInfo(UnityAction callBack)
        {
            //这部分知识点在 Unity网络开发基础当中有讲解
            //加载本地的DLL文件
#if UNITY_ANDROID
        UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
            UnityWebRequest reqDll =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
            yield return reqDll.SendWebRequest();
            //如果失败了
            if (reqDll.result != UnityWebRequest.Result.Success)
                print("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
            //读取加载的DLL数据
            byte[] dll = reqDll.downloadHandler.data;
            reqDll.Dispose();

#if UNITY_ANDROID
        UnityWebRequest reqPdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
            UnityWebRequest reqPdb =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
            yield return reqPdb.SendWebRequest();
            //如果失败了
            if (reqPdb.result != UnityWebRequest.Result.Success)
                print("加载Pdb文件失败" + reqPdb.responseCode + reqPdb.result);
            //读取加载的DLL数据
            byte[] pdb = reqPdb.downloadHandler.data;
            reqPdb.Dispose();

            //3.将加载的数据以流的形式(文件流或者内存流对象)传递给AppDomain对象中的LoadAssembly方法
            //注意 这里使用流 不要用完就关 一定要等到热更相关内容不使用了 再关闭
            dllStream = new MemoryStream(dll);
            pdbStream = new MemoryStream(pdb);
            //将我们两个文件的内存流用于初始化 appDomain 我们之后就可以通过该对象来执行我们对应的热更代码了
            appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());

            InitILRuntime();

            ILRuntimeLoadOverDoSomthing();

            //当ILRuntime启动完毕 想要在外部执行的内容
            callBack?.Invoke();
        }
    }
}

13.3 练习题

如果想在ILRuntime跨域继承时多继承我们应该怎么做?

ILRuntime跨域继承不支持多继承
我们需要在Unity中自定义一个类,完成多继承
然后在跨域继承时直接继承该类



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏