学习
实践
活动
专区
工具
TVP
写文章

ILRuntime学习

大家好,又见面了,我是你们的朋友全栈君。

ILRuntime介绍

ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新

ILRuntime优势

  • 访问C#工程的现成代码,无需额外抽象脚本API
  • 直接使用VS2015进行开发,ILRuntime的解译引擎支持.Net 4.6编译的DLL
  • 执行效率是L#的10-20倍
  • 选择性的CLR绑定使跨域调用更快速,绑定后跨域调用的性能能达到slua的2倍左右(从脚本调用GameObject之类的接口)
  • 支持跨域继承
  • 完整的泛型支持
  • 拥有Visual Studio的调试插件,可以实现真机源码级调试。支持Visual Studio 2015 Update3 以及Visual Studio 2017

ILRuntime demo工程

1. 下载ILRuntimeU3D demo

下载地址

2. unity打开项目

项目位置:\ILRuntimeU3D-master\ILRuntimeDemo

3. vs打开HotFix_project工程

\ILRuntimeU3D-master\HotFix_Project\HotFix_Project.sln

4. 修改HotFix_Project工程的引用

引用=》添加引用=》浏览 (如果存在可以先删除)

  1. UnityEngine

F:/Unity/Editor/Data/PlaybackEngines/windowsstandalonesupport/Variations/win64_nondevelopment_mono/Data/Managed/UnityEngine.dll

  1. UnityEngine.UI

F:/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll

  1. UnityEngine.CoreModule

F:/Unity/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll

5. 修改ILRuntimeU3D-master\ILRuntimeDemo*.csproj

<AllowUnsafeBlocks>false</AllowUnsafeBlocks>

6. 编译HotFix_Project工程

  • 右键=>生成
  • 生成成功,在\ILRuntimeU3D-master\ILRuntimeDemo\Assets\StreamingAssets\目录生成HotFix_Project.dll

7. \ILRuntimeU3D-master\HotFix_Project\HotFix_Project.sln 热更新脚本

8. \LRuntimeU3D-master\ILRuntimeDemo\ILRuntimeDemo.sln unity主工程


ILRunTime的使用

1. 下载ILRuntime相关文件

2. 检查热更新

  • 如果有热更,进行热更新

3. 实例化AppDomain(全局保存一个)

AppDomain appdomain;
appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();

4. 加载dll和pdb

#if UNITY_ANDROID
      www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
      www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
      while (!www.isDone)
          yield return null;
      if (!string.IsNullOrEmpty(www.error))
          UnityEngine.Debug.LogError(www.error);
      byte[] pdb = www.bytes;
      using (System.IO.MemoryStream fs = new MemoryStream(dll))
      {
          using (System.IO.MemoryStream p = new MemoryStream(pdb))
          {
              appdomain.LoadAssembly(fs, p, new Mono.Cecil.Pdb.PdbReaderProvider());
          }
      }

5. 初始化

  • InitializeILRuntime();
void InitializeILRuntime()
{
  //这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
}
  • OnHotFixLoaded();
void OnHotFixLoaded()
{
  //第一次方法调用
}

6. 各个地方的使用

主工程脚本调用热更脚本

  1. 调用类的静态方法
  • 方法1

无参数

//调用无参数静态方法,appdomain.Invoke("类名", "方法名", 对象引用, 参数列表);
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);

有参数

 //调用带参数的静态方法
 appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);
  • 方法2

无参数

//预先获得IMethod,可以减低每次调用查找方法耗用的时间
IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
//根据方法名称和参数个数获取方法
IMethod method = type.GetMethod("StaticFunTest", 0);
appdomain.Invoke(method, null, null);

有参数

IType intType = appdomain.GetType(typeof(int));
//参数类型列表
List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
paramList.Add(intType);
//根据方法名称和参数类型列表获取方法
method = type.GetMethod("StaticFunTest2", paramList, null);
appdomain.Invoke(method, null, 456);
  1. 调用类的成员方法

方法1 实例化对象

object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 })

调用方法

int id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj, null);

方法2 实例化对象

object obj2 = ((ILType)type).Instantiate();

调用方法

id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj2, null);
  1. 调用类的静态泛型方法
  • 方法1(直接调用) 构建参数
IType stringType = appdomain.GetType(typeof(string));
IType[] genericArguments = new IType[] { stringType };

调用方法

appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString");

方法2(先获取IMethod) 构建参数

paramList.Clear();
paramList.Add(intType);
genericArguments = new IType[] { intType };

调用方法

method = type.GetMethod("GenericMethod", paramList, genericArguments);
appdomain.Invoke(method, null, 33333);

热更DLL里面的委托实例传到Unity主工程用

  1. 注册适配器
//TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
appdomain.DelegateManager.RegisterMethodDelegate<int>();

//带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((action) =>
{
//转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action<int>转换成TestDelegateMethod
return new TestDelegateMethod((a) =>
{
    //调用委托实例
    ((System.Action<int>)action)(a);
});
});
//对于TestDelegateFunction同理,只是是将Func<int, string>转换成TestDelegateFunction
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>((action) =>
{
return new TestDelegateFunction((a) =>
{
    return ((System.Func<int, string>)action)(a);
});
});

//下面再举一个这个Demo中没有用到,但是UGUI经常遇到的一个委托,例如UnityAction<float>
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<float>>((action) =>
{
return new UnityEngine.Events.UnityAction<float>((a) =>
{
    ((System.Action<float>)action)(a);
});
});
  1. 使用
appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null);
appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest2", null, null);
TestMethodDelegate(789);
TestFunctionDelegate(098);
TestActionDelegate("Hello From Unity Main Project");

继承(热更脚本中的类继承主工程脚本中的类)

  1. 注册适配器
appdomain.RegisterCrossBindingAdaptor(new InheritanceAdapter());

InheritanceAdapter.cs

  1. 实例化

obj = appdomain.Instantiate(“HotFix_Project.TestInheritance”); “` 3. 使用

obj.TestAbstract(123);
obj.TestVirtual("Hello");

CLR重定向

使用到的地方(当我们需要挟持原方法实现,添加一些热更DLL中的特殊处理的时候,就需要CLR重定向了)

重定向Log方法

var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
appdomain.RegisterCLRMethodRedirection(mi, Log_11);

Log11的方法

//编写重定向方法对于刚接触ILRuntime的朋友可能比较困难,比较简单的方式是通过CLR绑定生成绑定代码,然后在这个基础上改,比如下面这个代码是从UnityEngine_Debug_Binding里面复制来改的
//如何使用CLR绑定请看相关教程和文档
unsafe static StackObject* Log_11(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
{
    //ILRuntime的调用约定为被调用者清理堆栈,因此执行这个函数后需要将参数从堆栈清理干净,并把返回值放在栈顶,具体请看ILRuntime实现原理文档
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
    StackObject* ptr_of_this_method;
    //这个是最后方法返回后esp栈指针的值,应该返回清理完参数并指向返回值,这里是只需要返回清理完参数的值即可
    StackObject* __ret = ILIntepreter.Minus(__esp, 1);
    //取Log方法的参数,如果有两个参数的话,第一个参数是esp - 2,第二个参数是esp -1, 因为Mono的bug,直接-2值会错误,所以要调用ILIntepreter.Minus
    ptr_of_this_method = ILIntepreter.Minus(__esp, 1);

    //这里是将栈指针上的值转换成object,如果是基础类型可直接通过ptr->Value和ptr->ValueLow访问到值,具体请看ILRuntime实现原理文档
    object message = typeof(object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack));
    //所有非基础类型都得调用Free来释放托管堆栈
    __intp.Free(ptr_of_this_method);

    //在真实调用Debug.Log前,我们先获取DLL内的堆栈
    var stacktrace = __domain.DebugService.GetStackTrance(__intp);

    //我们在输出信息后面加上DLL堆栈
    UnityEngine.Debug.Log(message + "\n" + stacktrace);

    return __ret;
}

CLR绑定

默认情况下,从热更DLL里调用Unity主工程的方法,是通过反射的方式调用的,这个过程中会产生GC Alloc,并且执行效率会偏低

  • 使用到的地方
    • 热更脚本调用主工程脚本
    • 但需要在主工程中提前做好相应工作
  • 注意事项
    • 一定要记得将CLR绑定的注册写在CLR重定向的注册后面,因为同一个方法只能被重定向一次,只有先注册的那个才能生效。
    • 可以选择性的对经常使用的CLR接口进行直接调用,从而尽可能的消除反射调用开销以及额外的GC Alloc
    • CLR绑定会生成较多C#代码,最终会增大包体和Native Code的内存耗用,所以只添加常用类型和频繁调用的接口即可
  1. 生成所需的绑定代码

unity=》ILRuntime=》Generate…

  1. 添加到绑定列表
public class CLRBindingTestClass
{
  public static float DoSomeTest(int a, float b)
  {
    return a + b;
  }
}

ILRuntimeCLRBinding.cs=》class ILRuntimeCLRBinding=》GenerateCLRBinding添加添加types.Add(typeof(CLRBindingTestClass));

  1. 生成绑定代码

  • unity=》ILRuntime=》Generate CLR Binding Code
  • ILRuntime->Generate CLR Binding Code by Analysis是ILRT1.2版新加入的功能,可以根据热更DLL自生成绑定代码
  1. 注册
  • ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
  1. 使用
 var type = appdomain.LoadedTypes["HotFix_Project.TestCLRBinding"];
 var m = type.GetMethod("RunTest", 0);
 appdomain.Invoke(m, null, null);

协程Coroutine

  1. 注册
  //使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator<object>,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter(详细请看04_Inheritance教程),Demo已经直接写好,直接注册即可
  appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
  1. 适配方法 CoroutineAdapter.cs
appdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null);

MonoBehaviour

注意事项

  • 在热更DLL里面使用MonoBehaviour是可以做到的,但是并不推荐这么做
  • 缺什么补什么
  1. 热更脚本使用AddComponent

重定向AddComponent

unsafe void SetupCLRRedirection2()
{
    //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
    var arr = typeof(GameObject).GetMethods();
    foreach (var i in arr)
    {
        if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1)
        {
            appdomain.RegisterCLRMethodRedirection(i, GetComponent);
        }
    }
}

AddComponent方法

unsafe static StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
{
    //CLR重定向的说明请看相关文档和教程,这里不多做解释
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;

    var ptr = __esp - 1;
    //成员方法的第一个参数为this
    GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
    if (instance == null)
        throw new System.NullReferenceException();
    __intp.Free(ptr);

    var genericArgument = __method.GenericArguments;
    //AddComponent应该有且只有1个泛型参数
    if (genericArgument != null && genericArgument.Length == 1)
    {
        var type = genericArgument[0];
        object res;
        if(type is CLRType)
        {
            //Unity主工程的类不需要任何特殊处理,直接调用Unity接口
            res = instance.AddComponent(type.TypeForCLR);
        }
        else
        {
            //热更DLL内的类型比较麻烦。首先我们得自己手动创建实例
            var ilInstance = new ILTypeInstance(type as ILType, false);//手动创建实例是因为默认方式会new MonoBehaviour,这在Unity里不允许
            //接下来创建Adapter实例
            var clrInstance = instance.AddComponent<MonoBehaviourAdapter.Adaptor>();
            //unity创建的实例并没有热更DLL里面的实例,所以需要手动赋值
            clrInstance.ILInstance = ilInstance;
            clrInstance.AppDomain = __domain;
            //这个实例默认创建的CLRInstance不是通过AddComponent出来的有效实例,所以得手动替换
            ilInstance.CLRInstance = clrInstance;

            res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance

            clrInstance.Awake();//因为Unity调用这个方法时还没准备好所以这里补调一次
        }

        return ILIntepreter.PushObject(ptr, __mStack, res);
    }

    return __esp;
}
  1. 热更脚本使用GetComponent

重定向GetComponent

unsafe void SetupCLRRedirection2()
{
    //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里
    var arr = typeof(GameObject).GetMethods();
    foreach (var i in arr)
    {
        if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1)
        {
            appdomain.RegisterCLRMethodRedirection(i, GetComponent);
        }
    }
}

GetComponent方法

unsafe static StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
{
    //CLR重定向的说明请看相关文档和教程,这里不多做解释
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;

    var ptr = __esp - 1;
    //成员方法的第一个参数为this
    GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject;
    if (instance == null)
        throw new System.NullReferenceException();
    __intp.Free(ptr);

    var genericArgument = __method.GenericArguments;
    //AddComponent应该有且只有1个泛型参数
    if (genericArgument != null && genericArgument.Length == 1)
    {
        var type = genericArgument[0];
        object res = null;
        if (type is CLRType)
        {
            //Unity主工程的类不需要任何特殊处理,直接调用Unity接口
            res = instance.GetComponent(type.TypeForCLR);
        }
        else
        {
            //因为所有DLL里面的MonoBehaviour实际都是这个Component,所以我们只能全取出来遍历查找
            var clrInstances = instance.GetComponents<MonoBehaviourAdapter.Adaptor>();
            for(int i = 0; i < clrInstances.Length; i++)
            {
                var clrInstance = clrInstances[i];
                if (clrInstance.ILInstance != null)//ILInstance为null, 表示是无效的MonoBehaviour,要略过
                {
                    if (clrInstance.ILInstance.Type == type)
                    {
                        res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance
                        break;
                    }
                }
            }
        }

        return ILIntepreter.PushObject(ptr, __mStack, res);
    }

    return __esp;
}
  1. 从主工程获取热更DLL的MonoBehaviour

获取热更dll中的MonoBehaviour

var type = appdomain.LoadedTypes["HotFix_Project.SomeMonoBehaviour2"] as ILType;
var smb = GetComponent(type);
var m = type.GetMethod("Test2");
appdomain.Invoke(m, smb, null);
    ```
    >主工程工程定义GetComponent方法
    ```c#
MonoBehaviourAdapter.Adaptor GetComponent(ILType type)
{
    var arr = GetComponents<MonoBehaviourAdapter.Adaptor>();
    for(int i = 0; i < arr.Length; i++)
    {
        var instance = arr[i];
        if(instance.ILInstance != null && instance.ILInstance.Type == type)
        {
            return instance;
        } 
    }
    return null;
}

反射Reflection(主工程中反射热更DLL中的类型)

  var it = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
  var type = it.ReflectionType;
  var ctor = type.GetConstructor(new System.Type[0]);
  var obj = ctor.Invoke(null);

试一下用反射给字段赋值

var fi = type.GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
fi.SetValue(obj, 111111);

用反射调用属性检查刚刚的赋值

var pi = type.GetProperty("ID");

LitJson(提供热更脚本中使用LitJson)

在使用LitJson前,需要对LitJson进行注册,注册方法很简单,只需要在ILRuntime初始化阶段,在注册CLR绑定之前,执行下面这行代码即可:

  1. 注册
  LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
  1. 使用

LitJson的使用很简单,JsonMapper类里面提供了对象到Json以及Json到对象的转换方法,具体使用方法请看热更项目中的代码 TestJson.cs

ValueTypeBinding(提供热更脚本使用)

使用的原因

  1. Vector3等Unity常用值类型如果不做任何处理,在ILRuntime中使用会产生较多额外的CPU开销和GC Alloc
  2. 我们通过值类型绑定可以解决这个问题,只有Unity主工程的值类型才需要此处理,热更DLL内定义的值类型不需要任何处理
  • 注册方法
appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
appdomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
appdomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());

#新知识

  1. unity测试代码运行的时间
  System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
  sw.Reset();
  sw.Start();
  sw.Stop();
  Debug.LogFormat("刚刚的方法执行了:{0} ms", sw.ElapsedMilliseconds);
  1. unity脚本代码性能(unity=》window=》profiler 不知道为什么我没有这个选项)
  Profiler.BeginSample("xxx");
  Profiler.EndSample();

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/209952.html原文链接:https://javaforall.cn

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!
本文分享自作者个人站点/博客:复制
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • ILRuntime热更新

    [Sugar]
  • Unity安装 ILRuntime插件

    unity2019.4.2f1c1 在package manager里面找不到ILRuntime插件

    全栈程序员站长
  • Jtro的技术分享:xlua热更新(二)资源差异化更新

    功能:资源打包,场景打包 资源差异化更新,打开热更的场景 我所了解的资源热更大概有下面几种: lua(xLua、tolua、ulua)、C#(DLL、ILR...

    张曙光
  • Unity下Bug修复神器,腾讯InjectFix开源啦!

    Unity 下 Bug 修复神器 InjectFix 开源啦! InjectFix 使用简单,小巧,合规且安全,经过多个项目应用反馈十分良好,即使你不打算用它来...

    腾讯开源
  • 学习笔记:深度学习之“学习”

    在上一篇文章中,我们谈到机器学习“学习”的是“规则”。进一步而言,机器学习需要一套评判机制来测量相应机器学习算法的性能。这套评判机制需要将当前输出与期望输出的“...

    Lauren的FPGA
  • 学习学习SpringSecurity

    SpringSecurity是Spring下的一个安全框架,与shiro 类似,一般用于用户认证(Authentication)和用户授权(Authorizat...

    mySoul
  • 元学习——让机器学习学会学习

    现代机器学习模型通常使用手工设计的特征和固定的学习算法,然后针对特定的任务从零开始进行训练,特别是在可以收集大量数据和可以使用大量计算资源的很多领域,深度学习都...

    绿盟科技研究通讯
  • 从学习 Paddle 开始学习深度学习

    优点 灵活性:PaddlePaddle支持广泛的神经网络结构和优化算法,很容易配置复杂的模型,如基于注意力(Attention)机制或复杂的内存(Memory)...

    用户1332428
  • 机器学习 学习笔记(17) 集成学习

    集成学习通过构建并结合多个学习器来完成学习任务,有时候也被称为多分类器系统(multi-classifier system)、基于委员会的学习(committe...

  • 机器学习、深度学习、演化学习

    机器学习是一门涉及统计学、系统辨识、逼近理论、神经网络、优化理论、计算机科学、脑科学等诸多领域的交叉学科,研究计算机怎样模拟或实现人类的学习行为,以获取新的知识...

    深度学习视觉
  • 快速学习-机器学习(监督学习)

    cwl_java
  • 《机器学习》学习笔记(七)——集成学习

    集成学习(ensemble learning)通过构建并结合多个学习器来提升性能。

    荣仔_最靓的仔
  • 机器学习 | 集成学习

    目的:让机器学习效果更好,多个弱学习器组合后可以成为强学习器,聚集多个学习器的预测来提高分类准确率

    week
  • flink学习-DataSourse学习

    Flink 做为一款流式计算框架,它可用来做批处理,即处理静态的数据集、历史的数据集;也可以用来做流处理,即实时的处理些实时数据流,实时的产生数据流...

    opprash
  • flink学习-DataSink学习

    sink的意思就是存储的意思,在flink流计算框架中,在获取流进行相应的数据转换和处理之后的下一步就是数据的存储了。一般就是存储到es,mysql,kafka...

    opprash
  • 【深度学习】深度学习

    深度学习的起源 深度学习(Deep Learning)是机器学习中一个非常接近AI的领域,其动机在于建立、模拟人脑进行分析学习的神经网络。深度学...

    陆勤_数据人网
  • 机器学习 学习笔记(14)k近邻学习

    k近邻是一种常用的监督学习方法,其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个邻居的信息来进行预测。通常...

  • 【学习强化学习】十三、模仿学习介绍[通俗易懂]

    模仿学习(imitation learning,IL)又叫做示范学习(learning from demonstration),学徒学习(apprentices...

    全栈程序员站长

扫码关注腾讯云开发者

领取腾讯云代金券