19.更多跨域调用-序列化库
19.1 知识点
复制之前的主入口调用
ILRuntimeManager.Instance.StartILRuntime(() =>
{
// 执行ILRuntime当中的一个静态函数
// 静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
ILRuntime中使用序列化库
使用概述
序列化库在我们开发中经常会用到,比如之前我们学过的 LitJson、Protobuf。但是这些库都存在于主工程中,所以在ILRuntime中使用序列化库需要对其进行修改。
注意:改写序列化库时,不能通过Activator来创建实例。
在热更工程声明结构体测试类
public class Lesson19_Test
{
public int testInt;
public string testStr;
public List<int> testList;
public Dictionary<string,int> testDictionary;
}
在ILRuntimeMain实例化测试类,进行序列化反序列化
Lesson19_Test test = new Lesson19_Test();
test.testInt = 99;
test.testStr = "韬韬韬";
test.testList = new List<int>() { 1, 2, 3, 4, 5 };
test.testDictionary = new Dictionary<string, int>() { { "1", 99 }, { "2", 88 }, { "3", 77 } };
//序列化Json字符串
string str = JsonMapper.ToJson(test);
Debug.Log(str);
//反序列化
Lesson19_Test test2 = JsonMapper.ToObject<Lesson19_Test>(str);
Debug.Log(test2.testInt);
Debug.Log(test2.testStr);
重新生成后直接运行会有报错,MissingMethodException: Default constructor not found for type,要进行一些处理
使用改好的LitJson库
步骤分析
- 初始化时注册:
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain)
- 正常使用LitJson进行序列化反序列化:
- 序列化:
JsonMapper.ToJson(对象)
- 反序列化:`JsonMapper.ToObject
- 序列化:
在ILruntimeManage初始化注册LitJson
// 注册Litjson
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain);
重新运行可以正常序列化反序列化了
如何自己改相关库
步骤
- 正确创建热更类型的实例(利用之前反射相关的创建方式)
- 获取泛型容器类的真实热更类型
- 序列化子对象
- 重定向泛型方法
查看去查看LitJson修改后的源码来分析加了哪些内容
点击转到RegisterILRuntimeCLRRedirection方法的定义,进去看做了什么,发现主要还是CLR重定向
public unsafe static void RegisterILRuntimeCLRRedirection(ILRuntime.Runtime.Enviorment.AppDomain appdomain)
{
foreach(var i in typeof(JsonMapper).GetMethods())
{
if(i.Name == "ToObject" && i.IsGenericMethodDefinition)
{
var param = i.GetParameters();
if(param[0].ParameterType == typeof(string))
{
appdomain.RegisterCLRMethodRedirection(i, JsonToObject);
}
else if(param[0].ParameterType == typeof(JsonReader))
{
appdomain.RegisterCLRMethodRedirection(i, JsonToObject2);
}
else if (param[0].ParameterType == typeof(TextReader))
{
appdomain.RegisterCLRMethodRedirection(i, JsonToObject3);
}
}
}
}
public unsafe static StackObject* JsonToObject(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = intp.AppDomain;
StackObject* ptr_of_this_method;
StackObject* __ret = ILIntepreter.Minus(esp, 1);
ptr_of_this_method = ILIntepreter.Minus(esp, 1);
System.String json = (System.String)typeof(System.String).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, mStack));
intp.Free(ptr_of_this_method);
var type = method.GenericArguments[0].ReflectionType;
var result_of_this_method = ReadValue(type, new JsonReader(json));
return ILIntepreter.PushObject(__ret, mStack, result_of_this_method);
}
public unsafe static StackObject* JsonToObject2(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = intp.AppDomain;
StackObject* ptr_of_this_method;
StackObject* __ret = ILIntepreter.Minus(esp, 1);
ptr_of_this_method = ILIntepreter.Minus(esp, 1);
JsonReader json = (JsonReader)typeof(JsonReader).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, mStack));
intp.Free(ptr_of_this_method);
var type = method.GenericArguments[0].ReflectionType;
var result_of_this_method = ReadValue(type, json);
return ILIntepreter.PushObject(__ret, mStack, result_of_this_method);
}
public unsafe static StackObject* JsonToObject3(ILIntepreter intp, StackObject* esp, IList<object> mStack, CLRMethod method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = intp.AppDomain;
StackObject* ptr_of_this_method;
StackObject* __ret = ILIntepreter.Minus(esp, 1);
ptr_of_this_method = ILIntepreter.Minus(esp, 1);
TextReader json = (TextReader)typeof(TextReader).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, mStack));
intp.Free(ptr_of_this_method);
var type = method.GenericArguments[0].ReflectionType;
var result_of_this_method = ReadValue(type, new JsonReader(json));
return ILIntepreter.PushObject(__ret, mStack, result_of_this_method);
}
总结
ILRuntime在使用第三方库时,为了能够正常对热更工程中声明的类对象进行使用,我们往往需要对其进行修改。修改第三方库对于大家来说可能有一定难度,所以首先去ILRuntime的群和社区中去找找有没有别人做好的。如果没有再尝试自己修改。
19.2 知识点代码
Lesson19_更多跨域调用_序列化库
using System.Collections;
using System.Collections.Generic;
using BaseFramework;
using UnityEngine;
public class Lesson19_更多跨域调用_序列化库 : MonoBehaviour
{
void Start()
{
#region 复制之前的主入口调用
ILRuntimeManager.Instance.StartILRuntime(() =>
{
//执行ILRuntime当中的一个静态函数
//静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
#endregion
#region 知识点一 ILRuntime中使用序列化库
//序列化库在我们开发当中经常会用到
//比如之前我们学过的 LitJson、Protobuf
//但是这些库都是存在于主工程中的
//那么当使用他们序列化反序列化热更工程中的对象时
//他们是不能识别的
//所以在ILRuntime中使用序列化库
//需要对其进行修改
//LitJson库获取:Demo工程中就有修改好的LitJson
//Protobuf库获取:https://gitee.com/cyecp/protobuf-net
//注意:
//改写序列化库时,不能通过Activator来创建实例
#endregion
#region 知识点二 使用改好的LitJson库
//1.初始化时注册
// LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain)
//2.正常使用LitJson进行序列化反序列化
// 序列化:JsonMapper.ToJson(对象)
// 反学历恶化:JsonMapper.ToObject<类型>
#endregion
#region 知识点三 如何自己改相关库
//1.正确创建热更类型的实例(利用之前反射相关的创建方式)
//2.获取泛型容器类的真实热热更类型
//3.序列化子对象
//4.重定向泛型方法
//去查看LitJson修改后的源码来分析加了哪些内容
#endregion
#region 总结
//ILRuntime在使用第三方库时
//为了能够正常的对热更工程中声明的类对象进行使用
//我们往往需要对其进行修改
//修改第三方库对于大家来说可能有一定难度
//所以首先去ILRuntime的群和社区中去找找有没有别人做好的
//如果没有再尝试自己修改
#endregion
}
}
Lesson19_Test
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace HotFix_Project
{
public class Lesson19_Test
{
public int testInt;
public string testStr;
public List<int> testList;
public Dictionary<string,int> testDictionary;
}
}
ILRuntimeMain
using LitJson;
using System;
using System.Collections;
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 CLR重定向和CLR绑定
////得到当前系统时间
//System.DateTime currentTime = System.DateTime.Now;
//for (int i = 0; i < 100000; i++)
//{
// Lesson14_更多跨域调用_CLR重定向和CLR绑定.TestFun(i, i);
//}
//Debug.Log("十万次循环花费的时间:" + (System.DateTime.Now - currentTime).TotalMilliseconds + "ms");
#endregion
#region 值类型绑定
//System.DateTime currentTime = System.DateTime.Now;
//Vector3 v1 = new Vector3(123, 54, 567);
//Vector3 v2 = new Vector3(342, 678, 123);
//float dot = 0;
//for (int i = 0; i < 1000000; i++)
//{
// dot += Vector3.Dot(v1, v2);
//}
//Vector2 v3 = new Vector2(12, 56);
//Vector2 v4 = new Vector2(123123, 45345);
//for (int i = 0; i < 1000000; i++)
//{
// dot += Vector3.Dot(v3, v4);
//}
//Debug.Log("值类型计算花费的时间:" + (System.DateTime.Now - currentTime).TotalMilliseconds + "ms");
#endregion
#region 热更工程中使用MonoBehaviour
//GameObject obj = new GameObject();
//ILRuntimeMono mono = obj.AddComponent<ILRuntimeMono>();
////由于Awake执行时机的特殊性 Awake会在脚本添加时自动执行 我们可以在添加了对应脚本后直接执行初始化相关逻辑即可
////无需通过事件或者委托的形式去触发它
//Debug.Log("Awake");
////mono.awakeEvent += () =>
////{
//// Debug.Log("Awake");
////};
//mono.startEvent += () =>
//{
// Debug.Log("Start");
//};
//mono.updateEvent += () =>
//{
// Debug.Log("Update");
//};
#endregion
#region 协同程序和异步函数
//Lesson17_更多跨域调用_协同程序和异步函数 lesson17 = GameObject.FindObjectOfType<Lesson17_更多跨域调用_协同程序和异步函数>();
//lesson17.StartCoroutine(Lesson17Courtinue());
//Lesson17Async();
#endregion
#region 序列化库
Lesson19_Test test = new Lesson19_Test();
test.testInt = 99;
test.testStr = "韬韬韬";
test.testList = new List<int>() { 1, 2, 3, 4, 5 };
test.testDictionary = new Dictionary<string, int>() { { "1", 99 }, { "2", 88 }, { "3", 77 } };
//序列化Json字符串
string str = JsonMapper.ToJson(test);
Debug.Log(str);
//反序列化
Lesson19_Test test2 = JsonMapper.ToObject<Lesson19_Test>(str);
Debug.Log(test2.testInt);
Debug.Log(test2.testStr);
#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
#region 协同程序和异步函数
public static IEnumerator Lesson17Courtinue()
{
Debug.Log(0);
yield return new WaitForSeconds(1f);
Debug.Log(1);
yield return new WaitForSeconds(1f);
Debug.Log(2);
yield return new WaitForSeconds(1f);
Debug.Log(3);
yield return new WaitForSeconds(1f);
}
public static async void Lesson17Async()
{
Debug.Log("Lesson17Async 1");
await Task.Delay(1000);
Debug.Log("Lesson17Async 2");
await Task.Delay(1000);
Debug.Log("Lesson17Async 3");
await Task.Delay(1000);
Debug.Log("Lesson17Async 4");
await Task.Delay(1000);
}
#endregion
}
}
ILRuntimeManager
using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.Utils;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
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>
unsafe 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()));
//注册协程跨域适配器
appDomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
//注册异步跨域适配器
appDomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());
// 值类型绑定注册
appDomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
appDomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
appDomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
//CLR重定向内容 要写到CLR绑定之前
//得到想要重定向类的Type
System.Type debugType = typeof(Debug);
//得到想要重定向方法的方法信息
MethodInfo methodInfo = debugType.GetMethod("Log", new System.Type[] { typeof(object) });
//进行CLR重定向
appDomain.RegisterCLRMethodRedirection(methodInfo, MyLog);
//注册Litjson
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain);
//注册CLR绑定信息
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
//初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
}
// MyLog函数用于记录日志,并输出调用栈信息
// 参数:
// __intp: ILRuntime解释器实例
// __esp: 栈指针
// __mStack: 托管堆栈
// __method: 当前方法
// isNewObj: 是否为新对象
unsafe StackObject* MyLog(ILIntepreter __intp, StackObject* __esp, List<object> __mStack, CLRMethod __method, bool isNewObj)
{
// 获取当前应用程序域
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
// 指针的声明
StackObject* ptr_of_this_method;
// 为了方便减法运算,计算栈顶指针位置
StackObject* __ret = ILIntepreter.Minus(__esp, 1);
// 获取栈顶参数的指针
ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
// 将栈顶参数转换为System.Object类型
System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (ILRuntime.CLR.Utils.Extensions.TypeFlags)0);
// 释放栈顶参数的指针
__intp.Free(ptr_of_this_method);
// 获取调用栈信息 是我们自己添加的
var stackTrace = __domain.DebugService.GetStackTrace(__intp);
// 输出日志信息和调用栈信息
UnityEngine.Debug.Log(@message + "\n" + stackTrace);
return __ret;
}
/// <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();
}
}
}
19.3 练习题
如何在ILRuntime中注册LitJson?
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appDomain)
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com