UH4L8——跨域继承Unity中的类

本章代码关键字

1
appDomain.RegisterCrossBindingAdaptor()        //注册一个生成出来的适配器类,传入适配器对象

代码生成模板:

1
2
3
4
5
6
//ILRuntimeCrossBinding.GenerateCrossbindAdapter方法内

using (System.IO.StreamWriter sw = new System.IO.StreamWriter("脚本生成路径和文件名"))
{
sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(要跨域继承的类), "生成的类的命名空间"));
}

跨域继承

ILRuntime支持在热更工程中继承Unity主工程中的类,这就是跨域继承

由于适配器在一些更为复杂的基类时,可能需要我们按照模板来手写一些内容
相对来说较为麻烦,如果没有特殊需求的情况下,或者是新开项目,都尽量不要出现跨域继承

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

注意

  1. ILRuntime不支持跨域(ILRuntime继承Unity)多继承
  2. 如果一定要跨域(ILRuntime继承Unity)多继承,就在主工程中用一个类将多继承关系梳理继承好,在热更工程中继承该类
  3. 跨域继承中,不能在基类的构造函数中调用该类的虚函数,会报错
  4. ILRuntime中的跨域继承主要指热更工程继承Unity工程中的类
    不存在Unity继承ILRuntime中的类一说,只需要记住,一般都是可变的(热更工程)使用不变的(Unity工程)内容

注:这里讨论的是不继承MonoBehaviour​的类,继承MonoBehaivour​的类相关有专门的笔记

如何进行跨域继承

  1. 在Unity工程中实现基类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    using UnityEngine;

    public abstract class Lesson11_Test
    {
    public int valuePublic;
    protected int valueProtected;

    public virtual int ValuePer
    {
    get; set;
    }

    public virtual void TestFun(string str)
    {
    Debug.Log("TestFun:" + str);
    }

    public abstract void TestAbstract(int i);
    }
  2. 在ILRuntime工程中继承基类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    using UnityEngine;

    namespace HotFix_Project
    {
    public class TestLesson11 : Lesson11_Test
    {
    public override int ValuePer
    {
    get => valueProtected;
    set => valueProtected = value;
    }

    public override void TestFun(string str)
    {
    base.TestFun(str);
    Debug.Log("TestFun2:" + str);
    }

    public override void TestAbstract(int i)
    {
    Debug.Log("TestAbstract: " + i);
    }
    }
    }

    但此时我们并不能直接使用继承出来的类,否则报错

    image

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

    image

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #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"));
    }
    AssetDatabase.Refresh();
    }
    }
    #endif

    我们需要在GenerateCrossbindAdapter​方法内,除了复制里面实现的代码以外,还需要修改如下内容:

    • new System.IO.StreamWriter()​内传入的参数为脚本的保存路径与文件名,路径以Assets/..​开头,脚本文件名后最好为..Adapter.cs
    • GenerateCrossBindingAdapterCode()​内,传入的第一个参数是typeof(要继承的类)
    • GenerateCrossBindingAdapterCode()​内,传入的第二个参数是生成的脚本所在的命名空间

    因此为了让Lesson11_Test​可以在ILRuntime里继承,我们需要向GenerateCrossbindAdapter​方法内添加如下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #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/Scripts/L11_继承/Lesson11_TestAdapter.cs"))
    {
    sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(Lesson11_Test), "ILRuntimeAdapter"));
    }

    AssetDatabase.Refresh();
    }
    }
    #endif

    最后点击菜单栏的 ILRuntime/生成跨域继承适配器​ 选项,即可生成适配器脚本

    image

    image

  4. 在初始化AppDomain​时,注册跨域继承适配器对象 appDomain.RegisterCrossBindingAdaptor(new 适配器类名());

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //ILRuntimeMgr内部

    //初始化ILRuntime相关的方法
    private void InitILRuntime()
    {
    //注册跨域继承相关的类
    appDomain.RegisterCrossBindingAdaptor(new ILRuntimeAdapter.Lesson11_TestAdapter());
    //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
    appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
    }

    注:关于这一步,除了初始化appDomain需要注册跨域继承相关的类以外
    最好是在Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCLRBinding.cs中
    InitILRuntime​ 这个方法内再统一注册跨域继承相关的类,具体原因在下一章详细讲解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class ILRuntimeCLRBinding
    {
    static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain domain)
    {
    //这里需要注册所有热更DLL中用到的跨域继承Adapter,否则无法正确抓取引用
    domain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
    domain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
    domain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());
    //在这里注册我们自定义的一些需要跨域继承的类
    domain.RegisterCrossBindingAdaptor(new ILRuntimeAdapter.Lesson11_TestAdapter());

    domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
    domain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
    domain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
    }
    }

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ILRuntimeMain
{

/// <summary>
/// 把逻辑处理权交给热更新工程,这是一个启动函数
/// </summary>
public static void Main()
{
TestLesson11 t = new TestLesson11();
t.TestFun("哈哈哈");
t.TestAbstract(99);
t.valuePublic = 100;
}
}

输出:image

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

跨域继承基本原理:ILRuntime中的跨域继承实际上并不是直接继承Unity中的基类,而是继承的适配器类

        基类(Unity中)  
         |  
      适配器类(Unity工程中的实际类型)  
         |  
        子类(ILRuntime中)

因此,即使是接口类型,也同样会生成一个对应的适配器类,供子类去继承,因此热更新的子类在继承主工程的接口的时候,实际上继承的是类

注意事项:

  1. 跨域继承时,不支持多继承,即同时继承类和接口,即使类和接口都生成了对应的适配器类并完成了注册

    因为接口也要生成一个适配器类,热更新的子类在继承主工程的接口的时候,实际上继承的是类,由于C#不允许多继承类,因此它会报错!

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    using UnityEngine;

    public abstract class Lesson11_Test
    {
    public int valuePublic;
    protected int valueProtected;

    public Lesson11_Test()
    {
    TestFun("测试");
    }

    public virtual int ValuePer
    {
    get; set;
    }

    public virtual void TestFun(string str)
    {
    Debug.Log("TestFun:" + str);
    }

    public abstract void TestAbstract(int i);
    }

    输出:image