11.更多跨域调用-委托调用
11.1 知识点
复制之前的热更新主入口调用启动入口
ILRuntimeManager.Instance.StartILRuntime(() =>
{
//执行ILRuntime当中的一个静态函数
//静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
Unity中自定义委托后在ILRuntime工程使用
在Unity中自定义委托后使用概述
ILRuntime中委托成员 关联ILRuntime工程中函数
- 直接常规使用即可
- 不会出现报错
Unity中委托成员 关联ILRuntime工程中函数
- 直接关联会出现报错,这里就涉及到委托成员的跨域
- 相当于Unity中的委托成员中存储了ILRuntime工程中的函数
- 就存在了跨域调用
我们需要进行以下处理:
- 可以通过报错信息中的提示
- 在进行初始化时进行代码的添加
- 主要有两部分:
- 注册委托(主要目的,避免IL2CPP打包裁剪报错)
- 注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)
注意:
- 委托的注册相关流程必须在主工程中完成,在ILRuntime中没用
- 为了避免添加自定义委托转换器
- 我们在使用委托时尽量使用System命名空间中的Action和Func
- 这样就不需要进行注册委托转换器了,只需要注册即可
在Unity中自定义两个委托
public delegate void MyUnityDel1();
public delegate int MyUnityDel2(int i, int j);
在ILRuntime工程声明两个测试函数
public static void Fun1()
{
Debug.Log("IL_Fun1");
}
public static int Fun2(int a, int b)
{
Debug.Log("IL_Fun2");
return a + b;
}
在ILRuntimeMain添加测试执行代码,重新生成。在ILRuntime中 申明 Unity中自定义委托变量 装载 ILRuntime的函数 正常使用
//在ILRuntime中 申明 Unity中自定义委托变量 装载 ILRuntime的函数 正常使用
MyUnityDel1 fun = Fun1;
fun();//IL_Fun1
MyUnityDel2 fun2 = Fun2;
int result = fun2(5, 6);//IL_Fun2
Debug.Log(result);//11
在Unity的 Lesson11_更多跨域调用_委托调用脚本 中定义两个自定义委托
public MyUnityDel1 fun;
public MyUnityDel2 fun2;
在ILRuntime工程找到我们在Unity中的Lesson11_更多跨域调用_委托调用脚本
Lesson11_更多跨域调用_委托调用 lesson11 = GameObject.FindObjectOfType<Lesson11_更多跨域调用_委托调用>();
在Unity中 申明 Unity中自定义委托变量 关联ILRuntime工程中的函数。直接设置Unity中自定义委托变量为ILRuntime工程中的函数会报错,报错提示中提示我们去注册委托
//直接设置Unity中自定义委托变量为ILRuntime工程中的函数会报错
//报错信息:KeyNotFoundException: Cannot find convertor for global::MyUnityDel1
lesson11.fun = Fun1;
lesson11.fun();
lesson11.fun2 = Fun2;
result = lesson11.fun2(7, 7);
Debug.Log(result);
我们在ILRuntimeManager的InitILRuntime中进行委托和委托转换器的注册。InitILRuntime的其他初始化逻辑执行是在appDomain.LoadAssembly后,设置主线程之前。具体注册MyUnityDel1和MyUnityDel2的委托和委托转换器。
//无参无返回值不需要委托注册
//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);
});
});
注册后运行Unity 关联ILRuntime工程中的函数代码可以正常运行
//使用自定义委托
//注册MyUnityDel1和MyUnityDel2的委托和委托转换器后才能正常执行
lesson11.fun = Fun1;
lesson11.fun();//IL_Fun1
lesson11.fun2 = Fun2;
result = lesson11.fun2(7,7);//IL_Fun2
Debug.Log(result);//14
注意
- 委托的注册相关流程必须在主工程中完成,在ILRuntime中没用
- 为了避免添加自定义委托转换器
我们在使用委托时 尽量使用System命名空间中的
Action和Fun
这样就不需要进行注册委托转换器了,只需要注册委托即可
在Unity的 Lesson11_更多跨域调用_委托调用脚本 定义Action型和Func型的委托变量
public Action funAction;
public Func<int, int, int> funFunc;
注释掉ILRuntimeManager的委托转换器注册,但是委托还是要注册,不然也会报错
// //无参无返回值不需要委托注册
// //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);
// });
// });
可以正常使用在Unity中 申明 的Action和Func委托装载ILruntime的函数并调用。
//使用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工程自定义委托后在Unity使用
在ILRuntime中自定义委托后使用概述
ILRuntime中委托成员 关联ILRuntime工程中函数
Unity中委托成员 关联ILRuntime工程中函数
- 一般不会出现基础工程中
- 使用还无法预知的可变代码
- 所以我们不需要考虑这种情况
在ILRuntimeMain定义委托
public delegate void MyILRuntimeDel1();
public delegate int MyILRuntimeDel2(int i, int j);
直接使用,重新生成就能调用
//在ILRuntime中自定义委托后使用 直接使用
MyILRuntimeDel1 funIL = Fun1;
funIL();//IL_Fun1
MyILRuntimeDel2 funIL2 = Fun2;
result = fun2(1, 1);//IL_Fun2
Debug.Log(result);//2
Unity中委托成员 关联ILRuntime工程中函数 不考虑
总结
在委托的跨域调用中,如果出现Unity中自定义委托跨域关联ILRuntime中函数,需要进行以下步骤:
- 注册委托(主要目的,避免IL2CPP打包裁剪报错)
- 注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)
注意:
- 委托的注册相关流程必须在主工程中完成
- 为了避免添加自定义委托转换器
- 我们在使用委托时尽量使用System命名空间中的Action和Func
- 这样就不需要进行注册委托转换器了,只需要注册即可
11.2 知识点代码
ILRuntimeManager
using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Threading;
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);
});
});
//初始化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();
}
}
}
Lesson11_更多跨域调用_委托调用
using System;
using System.Collections;
using System.Collections.Generic;
using BaseFramework;
using UnityEngine;
public delegate void MyUnityDel1();
public delegate int MyUnityDel2(int i, int j);
public class Lesson11_更多跨域调用_委托调用 : MonoBehaviour
{
public MyUnityDel1 fun;
public MyUnityDel2 fun2;
public Action funAction;
public Func<int, int, int> funFunc;
void Start()
{
#region 复制之前的热更新主入口调用
ILRuntimeManager.Instance.StartILRuntime(() =>
{
//执行ILRuntime当中的一个静态函数
//静态函数中 就是热更工程自己处理的逻辑了
ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});
#endregion
#region 知识点一 在Unity中自定义委托后使用
//1.ILRuntime中委托成员 关联ILRuntime工程中函数
//直接常规使用即可
//不会出现报错
//2.Unity中委托成员 关联ILRuntime工程中函数
//直接关联会出现报错,这里就涉及到委托成员的跨域
//相当于Unity中的委托成员中存储了ILRuntime工程中的函数
//就存在了跨域调用
//我们需要进行以下处理:
//可以通过报错信息中的提示
//在进行初始化时进行代码的添加
//主要有两部分:
//1.注册委托(主要目的,避免IL2CPP打包裁剪报错)
//2.注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)
//注意:
//1.委托的注册相关流程必须在主工程中完成,在ILRuntime中没用
//2.为了避免添加自定义委托转换器
// 我们在使用委托时 尽量使用System命名空间中的
// Action和Fun
// 这样就不需要进行注册委托转换器了,只需要注册即可
#endregion
#region 知识点二 在ILRuntime中自定义委托后使用
//1.ILRuntime中委托成员 关联ILRuntime工程中函数
//2.Unity中委托成员 关联ILRuntime工程中函数
//一般不会出现基础工程中
//使用还无法预知的可变代码
//所以我们不需要考虑这种情况
#endregion
#region 总结
//在委托的跨域调用中
//如果出现Unity中自定义委托跨域关联ILRuntime中函数
//需要进行
//1.注册委托(主要目的,避免IL2CPP打包裁剪报错)
//2.注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)
//注意:
//1.委托的注册相关流程必须在主工程中完成
//2.为了避免添加自定义委托转换器
// 我们在使用委托时 尽量使用System命名空间中的
// Action和Fun
// 这样就不需要进行注册委托转换器了,只需要注册即可
#endregion
}
}
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 委托调用
public static void Fun1()
{
Debug.Log("IL_Fun1");
}
public static int Fun2(int a, int b)
{
Debug.Log("IL_Fun2");
return a + b;
}
#endregion
}
}
11.3 练习题
ILRuntime的使用中,如果存在委托的跨域调用时(Unity委托跨域调用ILRuntime中函数)。建议使用Action和Fun两个系统自带委托的原因是什么?
只需要注册委托,不需要注册委托转换器.方便我们使用,减少注册代码量
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com