文章目录

  • 前言
  • 一、ILRuntime是什么?
  • 二、ILRuntime使用
    • 1.跨域委托
    • 2.跨域继承
    • 3.CLR绑定与重定向

前言

做游戏离不开热更新,目前市面上热更新方案用的比较多的是Lua(XLua,ToLua),最近又出现了基于C#的热更新 huatuo(已改名HybridCLR又叫wolong)。来不及学习了,以后用到了再去了解吧。
笔者入行做的第一个项目是利用ILRuntime进行热更新的,当时也是用的稀里糊涂的,一些坑点都是项目主程去解决的。这里做一个简单的回顾。


一、ILRuntime是什么?

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

2.实现原理
ILRuntime借助Mono.Cecil库来读取DLL的PE信息,以及当中类型的所有信息,最终得到方法的IL汇编码,然后通过内置的IL解译执行虚拟机来执行DLL中的代码来实现热更新功能。
查看ILRuntime源码你会发现,内部有一个很大的switch/case结构,就是针对基本上每一条IL指令码进行解释,同时维护一个Stackframe用于模拟cpu的函数调用的基本操作进行辅助解释。
ILRuntime中解释热更dll中的自定义类实例,在框架层这边都是对应的同一个warper,即ILTypeInstance。
ILTypeInstance会知道最终被调用方法的il指令内容,如果调用,则就是switch逐句去解析这个方法的IL代码。
这样一来就没有什么执行权限的问题,简单理解为读取一个普通文件,然后解析文件内容。
如果是反射处理这种情形,那就是真实的构建出一个新的类型,然后调用新类型的方法,这倒是会涉及到内存权限问题。

二、ILRuntime使用

当时使用的时候只记得有以下限制:
1.不能手动挂载热更Mono脚本,只能通过代码AddComponent
2.不能使用非System.Action/Fun类型的委托,需要手动注册委托类型转换
3.需要将类的成员初始化赋值删除,改为在方法内初始化
4.不允许使用ref和out
5.将热更脚本放在特定文件夹。通过定义宏,在日常开发中在Assembly-CSharp编译调用,打包时将热更脚本单独打DLL。

当时每天都在赶UI,没有去细究为什么这么做。
由于项目已经挂掉了,这里就不去纠结了。下面记录一下自己学习ILRuntime的历程。

1.跨域委托

只在热更新的DLL项目中使用的委托,是不需要任何额外操作的,就跟在通常的C#里那样使用即可。
如果你需要将委托实例传给ILRuntime外部使用,那则根据情况,你需要额外添加适配器或者转换器。

示例:同一个参数组合的委托,只需要注册一次即可

Action,以及Func委托需要在主工程注册适配器
// 无返回值委托
appDomain.DelegateManager.RegisterMethodDelegate<int, float>();
// 带返回值委托
appDomain.DelegateManager.RegisterFunctionDelegate<int, float, bool>();自定义委托需要额外添加转换器DelegateConvertor
// 自定义委托
delegate bool SomeFunction(int a, float b);
app.DelegateManager.RegisterDelegateConvertor<SomeFunction>((action) =>
{return new SomeFunction((a, b) =>{return ((Func<int, float, bool>)action)(a, b);});
});

官方建议:
尽量避免不必要的跨域委托调用。
尽量使用Action以及Func这两个系统内置万用委托类型。

2.跨域继承

如果你想在热更DLL项目当中继承一个Unity主工程里的类,或者实现一个主工程里的接口,你需要在Unity主工程中实现一个继承适配器。
为什么需要适配器?
1)防止热更层用到的框架层代码被裁减。
为什么会被裁减呢?因为Unity打包的时候真的不把这个热更dll看做dll,因为这个热更dll是脱离unity框架层的。自然在unity打包的时候,为了包体大小会把认为没有使用的代码全部过滤掉。这种情况下ILRuntime解释执行的时候,去反射调用框架层代码就会被视为错误,因为框架层不存在这些被调用的代码。

因为脱离了关系,那么如何在框架层中驱动的时候,可以同步驱动到热更层,这就成了一个问题。这就需要框架层引用热更层的相关instance去驱动 ,那么如何引用?这就是适配器的作用。适配器工作在框架层,其显式强调了需要引用驱动的类型实例,然后重写相关函数体内容,去实质调用 热更类型实例 的方法。具体参考MonoBehaviourAdapter即可理解。

ILRuntime提供了一个代码生成工具来自动生成跨域继承的适配器代码。

示例:

    void OnHotFixLoaded(){Debug.Log("首先我们来创建热更里的类实例");TestClassBase obj;Debug.Log("现在我们来注册适配器, 该适配器由ILRuntime/Generate Cross Binding Adapter菜单命令自动生成");appdomain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());Debug.Log("现在再来尝试创建一个实例");obj = appdomain.Instantiate<TestClassBase>("HotFix_Project.TestInheritance");Debug.Log("现在来调用成员方法");obj.TestAbstract(123);obj.TestVirtual("Hello");obj.Value = 233;Debug.LogFormat("obj.Value={0}", obj.Value);Debug.Log("现在换个方式创建实例");obj = appdomain.Invoke("HotFix_Project.TestInheritance", "NewObject", null, null) as TestClassBase;obj.TestAbstract(456);obj.TestVirtual("Foobar");obj.Value = 2333333;Debug.LogFormat("obj.Value={0}", obj.Value);}

3.CLR绑定与重定向

为什么需要绑定与重定向机制?
1)防止热更层用到的框架层代码被裁减。
2)加速热更代码的执行。
加速热更代码执行其实是ILRuntime解释每条il指令的时候,都会去现有缓存中查找当前指令是否为重定向函数,如果为重定向函数,则直接调用,如果不是重定向函数,则会反射调用。通过反射来调用接口调用效率会比直接调用低很多,反射传递函数参数时需要使用object[]数组,这样不可避免的每次调用都会产生不少GC Alloc。众所周知GC Alloc高意味着在Unity中执行会存在较大的性能问题。

ILRuntime提供了一个代码生成工具来自动生成CLR绑定代码。

生成代码示例:

namespace ILRuntime.Runtime.Generated
{unsafe class HelloWorld_Binding{public static void Register(ILRuntime.Runtime.Enviorment.AppDomain app){BindingFlags flag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;MethodBase method;Type[] args;Type type = typeof(global::HelloWorld);args = new Type[]{};method = type.GetMethod("TestHotfixInvokeMain", flag, null, args, null);app.RegisterCLRMethodRedirection(method, TestHotfixInvokeMain_0);args = new Type[]{};method = type.GetConstructor(flag, null, args, null);app.RegisterCLRMethodRedirection(method, Ctor_0);}static StackObject* TestHotfixInvokeMain_0(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);global::HelloWorld instance_of_this_method = (global::HelloWorld)typeof(global::HelloWorld).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)0);__intp.Free(ptr_of_this_method);instance_of_this_method.TestHotfixInvokeMain();return __ret;}static StackObject* Ctor_0(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj){ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;StackObject* __ret = ILIntepreter.Minus(__esp, 0);var result_of_this_method = new global::HelloWorld();return ILIntepreter.PushObject(__ret, __mStack, result_of_this_method);}}
}

先学到这,持续更新中。。。

参考链接:
github仓库地址:https://github.com/Ourpalm/ILRuntime
中文文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/index.html
使用ILRuntime遇到的一些问题
王王王渣渣ILRuntime系列

Unity 热更新方案之——ILRuntime相关推荐

  1. Unity热更新方案探索与讨论

    热更新必要性 App Store审核周期长 应用更新频繁 更新版本对留存数据有很大影响 Lua相关 Lua:脚本,解释性语言 LuaJit:扩展高效版本,支持编译成二进制代码. Tolua++:C/C ...

  2. 现有的几个Unity热更新方案该如何选择,各自的优缺点是什么?

    1.3 huatuo示例项目源码分析与启发 上一节我们安装huatuo的开发环境,然后运行示例项目,体验了huatuo做热更新,这节课我们来分析示例项目的源码,掌握huatuo做热更新的主要的步骤,让 ...

  3. 腾讯开源手游热更新方案,Unity3D下的Lua编程

    写在前面 \\ xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能.易用性.扩展性而广受好评.现在,腾讯已经将xLua开源到GitHub. ...

  4. Unity3D 热更新方案(集合各位专家的汇总)

    http://blog.csdn.net/guofeng526/article/details/52662994 热更新"这个词,在Unity3D的应用下,是有些语义错误的,但是作为大家都熟 ...

  5. Unity热更新机制

    前言 游戏上线后,难免会有一些测试阶段没发现的bug,bug这东西,可大可小. 如果出现重大bug,而又没有热更技术,那么我们为了修复bug就只能强制玩家去商店下载新包,那造成的玩家流失是非常可怕的. ...

  6. 手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    转载:https://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾 ...

  7. 【腾讯Bugly干货分享】手游热更新方案xLua开源:Unity3D下Lua编程解决方案

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:http://mp.weixin.qq.com/s/2bY7A6ihK9IMcA0bOFyB-Q 导语 xL ...

  8. C#热更新方案的选择

    前项目的C#热更方案 小甜甜的C#热更方案 前段时间 noodle 说他把 小甜甜 项目中他做的 C#热更方案 开源了. 这个方案是一个 骚操作,不过是针对 il2cpp 的,核心思想是更新 libi ...

  9. [Unity热更新]04.卸载方式

    [Unity热更新]04.卸载方式 参考链接: https://www.jianshu.com/p/b37ee8cea04c 1.AssetBundle.Unload(false):释放AssetBu ...

最新文章

  1. c语言运动会成绩统计报告,C语言程序设计运动会成绩统计系统1研究报告.doc
  2. layui循环数据并渲染_layui使用表格渲染获取行数据的例子
  3. VC/MFC中常用宏的含义
  4. go语言有哪些劣势?
  5. 使用VS2003创建WEB程序的时候出现AutoMation服务器不能创建对象错误
  6. ELK错误1_Kafka-Logstash-Elasticsearch过程,Elasticsearch报grokparsefailure错误
  7. python画饼图加牵引线_python-Matplotlib绘制分列式饼图并添加表格
  8. 安装配置OSA运维管理平台
  9. Redis安装[Windows]
  10. 前端学习(1386):多人管理项目6骨架
  11. 【bug】HbuilderX运行到微信小程序 报错
  12. Day05 egrep正则表达式sed
  13. 结构体链表赋值与删除
  14. 推荐一款颜值逆天且功能齐全的开源Shell工具
  15. PoE供电概述:PoE交换机是如何进行供电的?
  16. noob的python学习之路
  17. LAb3-自行车码表
  18. 网络文件传输工具,秒杀各种网络文件传送工具的镭速云
  19. Android性能优化—— 黑白屏启动优化
  20. 【转】Android屏幕适配全攻略(最权威的官方适配指导)

热门文章

  1. C++求loga(b)
  2. Docker 环境下常用数据库备份与恢复
  3. 高性能计算GPU解决方案系列教程三--高性能计算集群测试程序
  4. oracle去重差个数,Oracle基础(五):多表查询
  5. 电子计算机的应用范围有哪些,电子计算机的应用领域有哪些?
  6. Python-接受命令行参数-sys.argv
  7. 回归初心,近三万字详解 23 种设计模式(多图 + 代码)
  8. vue项目使用视频播放器vue-video-player
  9. 游戏软件功能测试用例编写
  10. 安卓手机卡顿怎么解决_安卓手机卡顿问题严重?关掉这个开关试试,手机瞬间流畅得像新的...