11.Unity和ILR互相调用委托

  1. 11.更多跨域调用-委托调用
    1. 11.1 知识点
      1. 复制之前的热更新主入口调用启动入口
      2. Unity中自定义委托后在ILRuntime工程使用
        1. 在Unity中自定义委托后使用概述
        2. 在Unity中自定义两个委托
        3. 在ILRuntime工程声明两个测试函数
        4. 在ILRuntimeMain添加测试执行代码,重新生成。在ILRuntime中 申明 Unity中自定义委托变量 装载 ILRuntime的函数 正常使用
        5. 在Unity的 Lesson11_更多跨域调用_委托调用脚本 中定义两个自定义委托
        6. 在ILRuntime工程找到我们在Unity中的Lesson11_更多跨域调用_委托调用脚本
        7. 在Unity中 申明 Unity中自定义委托变量 关联ILRuntime工程中的函数。直接设置Unity中自定义委托变量为ILRuntime工程中的函数会报错,报错提示中提示我们去注册委托
        8. 我们在ILRuntimeManager的InitILRuntime中进行委托和委托转换器的注册。InitILRuntime的其他初始化逻辑执行是在appDomain.LoadAssembly后,设置主线程之前。具体注册MyUnityDel1和MyUnityDel2的委托和委托转换器。
        9. 注册后运行Unity 关联ILRuntime工程中的函数代码可以正常运行
        10. 注意
        11. 在Unity的 Lesson11_更多跨域调用_委托调用脚本 定义Action型和Func型的委托变量
        12. 注释掉ILRuntimeManager的委托转换器注册,但是委托还是要注册,不然也会报错
        13. 可以正常使用在Unity中 申明 的Action和Func委托装载ILruntime的函数并调用。
      3. ILRuntime工程自定义委托后在Unity使用
        1. 在ILRuntime中自定义委托后使用概述
        2. 在ILRuntimeMain定义委托
        3. 直接使用,重新生成就能调用
        4. Unity中委托成员 关联ILRuntime工程中函数 不考虑
      4. 总结
    2. 11.2 知识点代码
      1. ILRuntimeManager
      2. Lesson11_更多跨域调用_委托调用
      3. ILRuntimeMain
    3. 11.3 练习题
      1. ILRuntime的使用中,如果存在委托的跨域调用时(Unity委托跨域调用ILRuntime中函数)。建议使用Action和Fun两个系统自带委托的原因是什么?

11.更多跨域调用-委托调用


11.1 知识点

复制之前的热更新主入口调用启动入口

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

Unity中自定义委托后在ILRuntime工程使用

在Unity中自定义委托后使用概述

  1. ILRuntime中委托成员 关联ILRuntime工程中函数

    • 直接常规使用即可
    • 不会出现报错
  2. Unity中委托成员 关联ILRuntime工程中函数

    • 直接关联会出现报错,这里就涉及到委托成员的跨域
    • 相当于Unity中的委托成员中存储了ILRuntime工程中的函数
    • 就存在了跨域调用

我们需要进行以下处理:

  • 可以通过报错信息中的提示
  • 在进行初始化时进行代码的添加
  • 主要有两部分:
    • 注册委托(主要目的,避免IL2CPP打包裁剪报错)
    • 注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)

注意:

  1. 委托的注册相关流程必须在主工程中完成,在ILRuntime中没用
  2. 为了避免添加自定义委托转换器
    • 我们在使用委托时尽量使用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

注意

  1. 委托的注册相关流程必须在主工程中完成,在ILRuntime中没用
  2. 为了避免添加自定义委托转换器
    我们在使用委托时 尽量使用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中自定义委托后使用概述

  1. ILRuntime中委托成员 关联ILRuntime工程中函数

  2. 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中函数,需要进行以下步骤:

  1. 注册委托(主要目的,避免IL2CPP打包裁剪报错)
  2. 注册委托转换器(主要目的,ILRuntime内部所有的委托都是以Action或Fun来存储的)

注意:

  1. 委托的注册相关流程必须在主工程中完成
  2. 为了避免添加自定义委托转换器
    • 我们在使用委托时尽量使用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

×

喜欢就点赞,疼爱就打赏