12.更多跨域调用-跨域继承Unity中的类
12.1 知识点
复制之前的主入口调用
ILRuntimeManager.Instance.StartILRuntime(() =>
{
//执行ILRuntime当中的一个静态函数
//静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
什么是跨域继承
ILRuntime支持在热更工程中继承Unity主工程中的类,这就是跨域继承。
注意:ILRuntime中的跨域继承主要指热更工程继承Unity工程中的类,不存在Unity继承ILRuntime中的类一说,只需要记住,一般都是可变的(热更工程)使用不变的(Unity工程)内容。
如何进行跨域继承
如何进行跨域继承步骤概述
- 在Untiy工程中实现基类。
- 在ILRuntime工程中继承基类。
- 通过工具生成跨域继承适配器:
ILRuntime\Assets\Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCrossBinding.cs
- 按照其中的模板,填写自己要为哪个类生成跨域继承适配器对象。
- 在初始化时,注册跨域继承适配器对象:
appDomain.RegisterCrossBindingAdaptor(new 适配器类名());
在Unity工程声明一个抽象父类
using UnityEngine;
public abstract class Lesson12_Father
{
public int valuePublic;
protected int valueProtected;
public virtual int ValuePor
{
get;
set;
}
public virtual void TestFun(string str)
{
Debug.Log("TestFun:" + str);
}
public abstract void TestAbstract(int i);
}
在ILruntime工程声明一个继承Unity工程的子类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace HotFix_Project
{
class Lesson12_Son : Lesson12_Father
{
public override int ValuePor
{
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);
}
}
}
在ILRuntimeMain中实例化子类并进行方法和属性的调用。重新生成后运行Unity会有TypeLoadException: Cannot find Adaptor for:Lesson12_Father报错。提示我们要生成适配器。
Lesson12_Son lesson12_Son = new Lesson12_Son();
lesson12_Son.TestFun("哈哈哈");
lesson12_Son.TestAbstract(99);
lesson12_Son.valuePublic = 100;
生成跨域适配器实际运行的是ILRuntimeCrossBinding脚本。路径是Assets\Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCrossBinding.cs。我们要准备修改这个脚本后再执行。
按照其中的模板,复制一份,填写自己要为哪个类生成跨域继承适配器对象。主要填写适配器类生成的路径,和绑定适配器的父类,命名空间可以根据自己的需求进行设置。
#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"));
}
AssetDatabase.Refresh();
}
}
#endif
点击生成适配器,会发现生成了父类适配器类脚本。但是现在运行还是会报错。报错信息:TypeLoadException: Cannot find Adaptor for:Lesson12_Father。因为还要注册跨域继承适配器对象。
在ILRuntimeManager的InitILRuntime中注册Lesson12_FatherAdapter,现在可以正常跨域继承了
//注册跨域继承的父类适配器
appDomain.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
总结
由于适配器在一些更为复杂的基类时,可能需要我们按照模板来手写一些内容,相对来说较为麻烦。如果没有特殊需求的情况下,或者是新开项目,都尽量不要出现跨域继承。
12.2 知识点代码
Lesson12_更多跨域调用_跨域继承Unity中的类
using System.Collections;
using System.Collections.Generic;
using BaseFramework;
using UnityEngine;
public class Lesson12_更多跨域调用_跨域继承Unity中的类 : MonoBehaviour
{
void Start()
{
#region 复制之前的主入口调用
ILRuntimeManager.Instance.StartILRuntime(() =>
{
//执行ILRuntime当中的一个静态函数
//静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
#endregion
#region 知识点一 什么是跨域继承
//ILRuntime支持在热更工程中继承Unity主工程中的类
//这就是跨域继承
//注意:
//ILRuntime中的跨域继承主要指热更工程继承Unity工程中的类
//不存在Unity继承ILRuntime中的类一说
//只需要记住,一般都是可变的(热更工程)使用不变的(Unity工程)内容
#endregion
#region 知识点二 如何进行跨域继承
//1.在Untiy工程中实现基类
//2.在ILRuntime工程中继承基类
//3.通过工具生成跨域继承适配器
// ILRuntime\Assets\Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCrossBinding.cs
// 按照其中的模板,填写自己要为哪个类生成跨域继承适配器对象
//4.在初始化时,注册跨域继承适配器对象
// appDomain.RegisterCrossBindingAdaptor(new 适配器类名());
#endregion
#region 总结
//由于适配器在一些更为复杂的基类时,可能需要我们按照模板来手写一些内容
//相对来说较为麻烦
//如果没有特殊需求的情况下,或者是新开项目
//都尽量不要出现跨域继承
#endregion
}
}
Lesson12_Father
using UnityEngine;
public abstract class Lesson12_Father
{
public int valuePublic;
protected int valueProtected;
public virtual int ValuePor
{
get;
set;
}
public virtual void TestFun(string str)
{
Debug.Log("TestFun:" + str);
}
public abstract void TestAbstract(int i);
}
Lesson12_Son
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
namespace HotFix_Project
{
class Lesson12_Son : Lesson12_Father
{
public override int ValuePor
{
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);
}
}
}
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 委托调用
public static void Fun1()
{
Debug.Log("IL_Fun1");
}
public static int Fun2(int a, int b)
{
Debug.Log("IL_Fun2");
return a + b;
}
#endregion
}
}
ILRuntimeCrossBinding
#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"));
}
AssetDatabase.Refresh();
}
}
#endif
Lesson12_FatherAdapter
using System;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG
using AutoList = System.Collections.Generic.List<object>;
#else
using AutoList = ILRuntime.Other.UncheckedList<object>;
#endif
namespace Lesson12_FatherNameSpace
{
public class Lesson12_FatherAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return typeof(global::Lesson12_Father);
}
}
public override Type AdaptorType
{
get
{
return typeof(Adapter);
}
}
public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adapter(appdomain, instance);
}
public class Adapter : global::Lesson12_Father, CrossBindingAdaptorType
{
CrossBindingFunctionInfo<System.Int32> mget_ValuePor_0 = new CrossBindingFunctionInfo<System.Int32>("get_ValuePor");
CrossBindingMethodInfo<System.Int32> mset_ValuePor_1 = new CrossBindingMethodInfo<System.Int32>("set_ValuePor");
CrossBindingMethodInfo<System.String> mTestFun_2 = new CrossBindingMethodInfo<System.String>("TestFun");
CrossBindingMethodInfo<System.Int32> mTestAbstract_3 = new CrossBindingMethodInfo<System.Int32>("TestAbstract");
bool isInvokingToString;
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
public Adapter()
{
}
public Adapter(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
public ILTypeInstance ILInstance { get { return instance; } }
public override void TestFun(System.String str)
{
if (mTestFun_2.CheckShouldInvokeBase(this.instance))
base.TestFun(str);
else
mTestFun_2.Invoke(this.instance, str);
}
public override void TestAbstract(System.Int32 i)
{
mTestAbstract_3.Invoke(this.instance, i);
}
public override System.Int32 ValuePor
{
get
{
if (mget_ValuePor_0.CheckShouldInvokeBase(this.instance))
return base.ValuePor;
else
return mget_ValuePor_0.Invoke(this.instance);
}
set
{
if (mset_ValuePor_1.CheckShouldInvokeBase(this.instance))
base.ValuePor = value;
else
mset_ValuePor_1.Invoke(this.instance, value);
}
}
public override string ToString()
{
IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
m = instance.Type.GetVirtualMethod(m);
if (m == null || m is ILMethod)
{
if (!isInvokingToString)
{
isInvokingToString = true;
string res = instance.ToString();
isInvokingToString = false;
return res;
}
else
return instance.Type.FullName;
}
else
return instance.Type.FullName;
}
}
}
}
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 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()));
//初始化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();
}
}
}
12.3 练习题
如果想要ILRuntime跨域继承Unity中的类,我们必须做得两步操作是什么?
- 使用ILRuntime自带工具为对应类生成跨域继承适配器,在ILRuntimeCrossBinding脚本中添加对应的生成代码后点击工具栏中的生成按钮
- 注册生成的跨域继承适配器
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com