找回密码
 立即注册
查看: 412|回复: 5

如何评价C#热更框架HybridCLR?

[复制链接]
发表于 2023-6-17 12:02 | 显示全部楼层 |阅读模式
原huatuo,改名为HybridCLR
发表于 2023-6-17 12:03 | 显示全部楼层
以往最多的是lua系,多恶心就不说了,腾讯最近搞了个puerts,为什么没用c#呢,多半也是因为热更,看来热更还是大家的硬需求,但是ts也有问题,单线程,执行效率低于静态类型语言,另外动态类型和游戏引擎自身的c++结合是他的gc, 互操作,生命周期,类型转换等问题都是很复杂的。我看到有答主举了一大堆自己觉得的hybridclr的问题,那相比之下我觉得lua和ts的本质问题更多,这么在意风险我就只敢用原生了,在虚幻下我都不敢用puerts,在u3d下那当然原生c#最稳,所以要稳,还是原生代码为主,少量热更机制补充。
反正不急的话可以多等等hybridclr的项目公开出来观察下,观察下情况又没有成本和风险,毕竟是热更,我觉得用国内好的民间组件是很舒服的一件事情,你想要提啥需求就直接去qq群交流,开发者通常很能够考虑各种合理需求的,不像老外油盐不进,说的东西他们压根不理解。
发表于 2023-6-17 12:04 | 显示全部楼层
最近huatuo(华佗)热更新解决方案火爆了unity开发圈,起初我觉得热更新嘛,不就是内置一个脚本解释器+脚本语言开发,如xLua, ILRuntime, puerts。Huatuo又能玩出什么花样,凭什么会这么NB,引起了那么多程序员的关注与称赞呢?带着这些问题我详细的看了huatuo的资料,阅读了示例项目+huatuo源码,我也瞬间成了一位”佗粉”。接下来更新一系列的文字教程+视频教程来详细的讲解huatuo热更新。
对惹,这里有一个游戏开发交流小组,希望大家可以点击进来一起交流一下开发经验呀
要掌握huatuo热更新,我们先搞懂一些底层的概念与原理,搞懂这些是掌握huatuo的关键。本节我将从以下3个方面来详细的讲解huatuo热更新解决方案:
(1) il2cpp是什么? AOT是什么?
(2) huatuo热更新的技术原理;
(3) huatuo热更方案的革命性优势;
il2cpp是什么? AOT是什么?
在说il2cpp之前,先说说mono, 在mono之前,C#虽然很好,但是只在windows家族平台上使用,就这点C#与Java就无法比。于是微软公司向ECMA申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO标准(ISO/IEC 23270)。意味着只要你遵守CLI(Common Language Infrastructure),第三方可以将任何一种语言实现到.Net平台之上。有了CLI的标准,Mono就诞生了, 该项目的目标是创建一系列符合ECMA标准(Ecma-334和Ecma-335)的.NET工具,包括C#编译器和通用语言架构。与微软的.NET Framework(共通语言运行平台)不同,Mono项目不仅可以运行于Windows系统上,还可以运行于Linux,FreeBSD,Unix,OS X和Solaris,甚至一些游戏平台,例如:Playstation 3,Wii或XBox 360之上。Mono使得C#这门语言相对于微软的.Net有了很好的跨平台能力。
.Net Framework运行时库Mono使用自己的Mono VM。 加上C#本身快速友好的开发能力,最终使得Unity团队在创建之初就决定将Mono,C#作为其核心。
接下来引出重要的一个概念”IL“。IL的全称是 Intermediate Language,很多时候还会看到CIL(Common Intermediate Language,特指在.Net平台下的IL标准)。在Unity中,IL和CIL表示的是同一个东西:它是一种属于通用语言架构和.NET框架的低阶(lowest-level)的编程语言。将.NET框架的语言编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的,它运行在.net虚拟机上。
CLI标准出来后,又出现一个项目:IL2CPP,把IL转成静态的c++代码文件,由本地编译器编译成二进制机器指令。由于C#这样的高级语言都有垃圾回收等机制,所以IL转成静态的c++代码后,还有一个IL2CPP的runtime(IL2CPP VM)用来支撑这些高级语言特性。通过IL2CPP技术,我们IL代码转成本地机器码,获得很好的性能。Unity也采用了这个技术,用unity开发的C#代码可以通过.net 转成IL代码,再通过IL2CPP转成静态c++文件,然后编译成本地机器码运行。为什么Unity采用IL2CPP呢?主要原因有:
a:Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成。
b: Mono版本授权受限, 换IL2CPP,IL2CPP VM这套完全自己开发的组件,就解决了授权问题。
c: 提高运行效率,换成IL2CPP以后,程序编译成了硬件目标机器指令,运行效率提升1.5-2.0倍。
Unity基于IL2CPP 的架构原理,如图1.1-1所示:


图1.1-1 Unity IL2CPP 运行示意图
最后一个概念AOT(Ahead of time),AOT技术指的是将高级开发语言直接转成传统的编译型编程语言(如C/C++),再编译成机器指令代码在硬件上运行。IL2CPP可以成为AOT技术。
huatuo热更新的技术原理
铺垫完IL2CPP,AOT等概念后,接下来就来说huatuo了。由图1.1-1可知Unity最终打包运行为:AOT(本地机器指令执行)+, IL2CPP VM(提供基础服务支撑,如gc)。对于IL2CPP 底层运行模式而言,运行的时候是数据内存对象+代码机器指令两个部分。huatuo做热更就是扩展了IL2CPP VM的服务,让它在使用原来数据内存对象的情况下,扩展了解释执行IL代码的功能(注意这里的使用”原来数据内存对象”很重要)。让IL2CPP的运行模式变为: 数据内存对象+AOT代码机器指令+Interpreter IL指令解释执行的3个部分。huatuo做热更的时候,我们只需要利用Unity ADF(asmdef, 程序集定义文件)的机制,让Unity对某一部分单独编译出一个IL指令的.dll。热更时,IL2CPP_huatuo就可以装载IL指令.dll, 由IL2CPP_huatuo来解释执行。这样AOT模式+huatuo IL指令解释执行 (Interpreter)让huatuo能具备热更新的功能。同时huatuo解释执行使用的是原来AOT的数据内存对象,所以huatuo热更新不会有其它热更新方案需要的接口导出,跨域调用等一系列问题。让开发者在不用做任何特殊处理的情况下,直接使用普通的unity开发技术能做到热更新。由于可以直接使用AOT的数据内存对象,内存占用,性能都会更好。有了这些优势(不用做任何代码上的处理就能实现热更),难怪Unity开发者都欢呼雀跃,因为他们终于能丢掉xLua, ILRuntime又笨又重的壳,直接从底层解决问题。所以我认为未来的huatuo会成为Unity热更的主流方案。
huatuo热更的革命性优势
分析完原理后,我们来看下huatuo的革命性优势:
huatuo第1个优势是基于AOT(本地机器代码执行)+Interpreter (IL解释执行)使用同一个内存数据对象,没有跨域访问的问题。我们来拿xLua或ILRuntime热更方案来举例,这些方案都有一条原则,尽量减少与Unity C#层的交互,但是这种交互又避免不了而且量大,比如我们要在逻辑热更代码里面访问 Unity C#的GameObject对象数据,最终在运行的时候,GameObject 会在AOT模式下的原生内存数据结构对象。由于xLua或ILRuntime有自己的虚拟机,所以不能直接访问原生GameObject数据对象,往往要把访问里面的数据包装成函数,这样性能开销就大大的增加了。而huatuo是在IL2CPP模式下的解释执行,直接可以访问原生的数据对象。
huatuo第2个优势是我们的逻辑代码更新后(1.0版本到2.0版本),如果你发布新版本2.0(重新安装新版本的app),可以直接把更新的逻辑,直接使用AOT编译出来,不用解释执行,从而获得AOT的性能。而基于xLua, ILRuntime的热更方案开发的代码(1.0版本到2.0版本),用户即使重新安装2.0客户端后,还是解释执行,新版本的性能无法达到AOT的性能级别。
huatuo 第3个好处是相比传统的Lua或ILRuntime热更,他能更新任意部分的代码。不用像Lua或ILRuntime一样,分热更代码+框架代码,框架代码有bug还不能热更。
有了这些革命性的优势,你没有理由不关注+使用huatuo。
附:视频教程
Unity / 精选推荐  huatuo 热更新原理与实战详解

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-6-17 12:04 | 显示全部楼层
huatuo第1个优势是基于AOT(本地机器代码执行)+Interpreter (IL解释执行)使用同一个内存数据对象,没有跨域访问的问题。我们来拿xLua或ILRuntime热更方案来举例,这些方案都有一条原则,尽量减少与Unity C#层的交互,但是这种交互又避免不了而且量大,比如我们要在逻辑热更代码里面访问 Unity C#的GameObject对象数据,最终在运行的时候,GameObject 会在AOT模式下的原生内存数据结构对象。由于xLua或ILRuntime有自己的虚拟机,所以不能直接访问原生GameObject数据对象,往往要把访问里面的数据包装成函数,这样性能开销就大大的增加了。而huatuo是在IL2CPP模式下的解释执行,直接可以访问原生的数据对象。
  huatuo第2个优势是我们的逻辑代码更新后(1.0版本到2.0版本),如果你发布新版本2.0(重新安装新版本的app),可以直接把更新的逻辑,直接使用AOT编译出来,不用解释执行,从而获得AOT的性能。而基于xLua, ILRuntime的热更方案开发的代码(1.0版本到2.0版本),用户即使重新安装2.0客户端后,还是解释执行,新版本的性能无法达到AOT的性能级别。
  huatuo 第3个好处是相比传统的Lua或ILRuntime热更,他能更新任意部分的代码。不用像Lua或ILRuntime一样,分热更代码+框架代码,框架代码有bug还不能热更。
有了这些革命性的优势,你没有理由不关注+使用huatuo。
发表于 2023-6-17 12:05 | 显示全部楼层
HybridCLR
HybridCLR介绍

HybridCLR是一个特性完整、零成本、高性能、低内存的近乎完美的Unity全平台原生c#热更方案。
HybridCLR扩充了il2cpp的代码,使它由纯AOT runtime变成‘AOT+Interpreter’ 混合runtime,进而原生支持动态加载assembly,使得基于il2cpp backend打包的游戏不仅能在Android平台,也能在IOS、Consoles等限制了JIT的平台上高效地以AOT+interpreter混合模式执行。
HybridCLR开创性地实现了 `differential hybrid dll` 技术。即可以对AOT dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行,让热更新的游戏逻辑的运行性能基本达到原生AOT的水平。
基础概念

CLR

CLR即 Common Language Runtime,中文叫公共语言运行时,是让 .NET 程序执行所需的外部服务的集合,.NET 平台的核心和最重要的组件,类似于 Java 的 JVM。更详细介绍请看 公共语言运行时 (CLR) 概述
il2cpp

il2cpp是Unity开发的跨平台CLR解决方案。诞生它的一个关键原因是Unity需要跨平台运行,但一些平台如iOS这种禁止了JIT,导致依赖了JIT的官方CLR虚拟机无法运行,必须使用AOT技术将mananged程序提前转化为目标平台的静态原生程序后再运行。而mono虽然也支持AOT,但性能较差以及跨平台支持不佳。
il2cpp方案包含一套AOT运行时以及一套dll到C++代码及元数据的转换工具,使得原始的c#开发的代码最终能在iOS这样的平台运行起来。
il2cpp与热更新

很不幸,不像mono有Hybrid mode execution,支持动态加载dll,il2cpp是一个纯静态的AOT运行时,不支持运行时加载dll,因此不支持热更新。
目前unity平台的主流热更新方案xlua、ILRuntime之类都是引入一个第三方vm(virtual machine),在vm中解释执行代码,来实现热更新。限于篇幅我们只分析使用c#为开发语言的热更新方案。这些热更新方案的vm与il2cpp是独立的,意味着它们的元数据系统是不相通的,在热更新里新增一个类型是无法被il2cpp所识别的(例如通过System.Activator.CreateInstance是不可能创建出这个热更新类型的实例),这种看起来像、实际上却又不是的伪CLR虚拟机,在与il2cpp这种复杂的CLR运行时交互时,产生极大量的兼容性问题,另外还有严重的性能问题。
一个大胆的想法是,是否有可能对il2cpp运行时进行扩充,添加interpreter模块,进而实现mono hybrid mode execution 这样机制?这样一来就能彻底支持热更新了,并且兼容性极佳。对开发者来说,除了以解释模式运行的部分执行得比较慢,其他方面跟标准的运行时没有区别。
对il2cpp加以了解并且深思熟虑后的答案是——确实是可行的!具体分析参见 关于HybridCLR可行性的思维实验 。这个想法诞生了HybridCLR,unity平台第一个支持ios的跨平台原生c#热更新方案!
原理

HybridCLR扩充了il2cpp运行时,将它由AOT运行时改造为'AOT + interpreter'双引擎的混合运行时,进而完美支持在iOS这种禁止JIT的平台上以解释模式无缝地运行动态加载的dll。如下图所示:


更具体一些,至少需要实现以下功能:

  • 加载和解析dll元数据
  • 动态注册元数据,其中关键为hook动态函数的执行流到解释器函数
  • 实现一个高效正确的解释器
  • 正确处理gc及多线程等运行时机制
特性

标准运行时特性

近乎完整实现了ECMA-335规范,不支持的特性仅包括:

  • 不支持delegate的BeginInvoke, EndInvoke。纯粹是觉得没必要实现
  • 不支持 MonoPInvokeCallbackAttribute。意味着你如果同时还接了lua,你没法直接将热更新的c#函数注册到lua中,但有一个不复杂的办法能做到这点。
由于HybridCLR极其完整的实现,使用HybridCLR后的c#开发体验跟editor下mono开发几乎完全相同(除了调用一些il2cpp没实现的.net framework函数,非专家级别的开发者难以构造出HybridCLR不支持的用例)。
另外,由于是运行时级别的实现,HybridCLR支持这些特性的同时,不需要你额外生成或者调整任何代码。对于开发者来说,相比Unity下原生c#开发,零额外的学习和开发成本。
AOT相关特性

由于il2cpp AOT模块的存在,il2cpp比于标准运行时多了一些不存在的机制,因此HybridCLR也有一些额外的特性

  • 支持使用 interpreter assembly替换 AOT assembly(限制:必须不存在其他AOT assembly对它的直接引用)
  • 支持补充元数据机制,彻底支持AOT泛型,参见AOT泛型原理
  • 支持AOT hotfix,可以修复AOT模块的bug
  • 支持任意c#函数注册到lua之类的虚拟机,不限于static函数,并且也不需要MonoPInvokeCallbackAttribute。条件是注册和回调方式需要略微调整
  • 开创性地实现了 `differential hybrid dll` 技术。即可以将某个热更新dll先AOT形式打包,后面可以对该dll任意增删改,会智能地让变化或者新增的类和函数以interpreter模式运行,但未改动的类和函数以AOT方式运行。这意味着热更新的游戏逻辑的运行性能将接近原生AOT的水平。
Unity相关特性


  • 完美支持Unity的 assembly def模块机制。
  • 完美支持代码中挂载热更新脚本,无使用场景限制
  • 完美支持资源上挂载热更新脚本,但要求打包工作流有少许调整,参见MonoBehaviour工作流
与其他流行的c#热更新方案的区别

从原理来说,HybridCLR几乎将Unity C#原生热更新技术做到理论上的极限,与当前所有主流热更新方案不在一个层次。
本质比较

HybridCLR是原生的c#热更新方案。通俗地说,il2cpp相当于mono的aot模块,HybridCLR相当于mono的interpreter模块,两者合一成为完整mono。HybridCLR使得il2cpp变成一个全功能的runtime,原生(即通过System.Reflection.Assembly.Load)支持动态加载dll,从而支持ios平台的热更新。
正因为HybridCLR是原生runtime级别实现,热更新部分的类型与主工程AOT部分类型是完全等价并且无缝统一的。可以随意调用、继承、反射、多线程,不需要生成代码或者写适配器。
其他热更新方案则是独立vm,与il2cpp的关系本质上相当于mono中嵌入lua的关系。因此类型系统不统一,为了让热更新类型能够继承AOT部分类型,需要写适配器,并且解释器中的类型不能为主工程的类型系统所识别。特性不完整、开发麻烦、运行效率低下。
实际使用体验或者特性比较


  • HybridCLR学习和使用成本几乎为零。HybridCLR让il2cpp变成全功能的runtime,学习和使用成本几乎为零,几乎零侵入性。而其他方案则有大量的坑和需要规避的规则,学习和使用成本,需要对原项目作大量改造。
  • HybridCLR可以使用所有c#的特性。而其他方案往往有大量的限制。
  • HybridCLR中可以直接支持使用和继承主工程中的类型。其他方案要写适配器或者生成代码。
  • HybridCLR中热更新部分元数据与AOT元数据无缝统一。像反射代码能够正常工作的,AOT部分也可以通过标准Reflection接口创建出热更新对象。其他方案做不到。
  • HybridCLR对多线程支持良好。像多线程、ThreadStatic、async等等特性都是HybridCLR直接支持,其他方案除了async特性外均难以支持。
  • HybridCLR中Unity工作流与原生几乎完全相同。HybridCLR中热更新MonoBehaviour可以直接挂载在热更新资源上,并且正确工作。其他方案不行。
  • HybridCLR兼容性极高。各种第三方库只要在il2cpp下能工作,在HybridCLR下也能正常工作。其他方案往往要大量魔改源码。
  • HybridCLR内存效率极高。HybridCLR中热更新类型与主工程的AOT类型完全等价,占用一样多的空间。其他方案的同等类型则是假类型,不仅不能被runtime识别,还多占了数倍空间。
  • HybridCLR执行效率高。HybridCLR中热更新部分与主工程AOT部分交互属于il2cpp内部交互,效率极高。而其他方案则是独立虚拟机与il2cpp之间的效率,不仅交互麻烦还效率低下。
运行性能

实际性能如理论估计,全面并且大幅胜出当前主流的xlua、puerts、ILRuntime之类的热更新方案。

  • 基础指令(数值计算及条件跳转等指令),由于各个语言之间差距不大,因此胜出不明显
  • 对象模型指令。由于没有跨语言交互的成本,几乎是数倍到数十倍的提升(如果指令自身消耗特别大,则差距不那么明显)
性能测试用例来自ILRuntime提供的标准测试用例,测试项目来自Don't worry的github仓库。
测试结果显示,绝大多数测试用例都有数倍到数十倍的性能差距,差距极其夸张。唯独数值计算跟xlua有少量劣势,这是因为当前HybridCLR 未对指令作任何优化 ,后续优化版本大多数基础指令都将有100-300%的性能提升。



内存

HybridCLR是运行时级别的实现,因为热更新的脚本,除了执行代码是以解释模式执行,其他方式跟AOT部分的类型是完全相同的,包括占用内存。
对象内存大小对比

lua的计算规则略复杂,参见第三方文章。空table占56字节,每多一个字段至少多占32字节。
ILRuntime的类型除了enum外统一以IlTypeInstance表达,空类型占72字节,每多一个字段至少多用16字节。如果对象中包含引用类型数据,则整体又至少多24字节,并且每多一个object字段多8字节。

类型XluaILRuntimeHybridCLR
V188881
V21201048
V318416824
C1888824
C212010424
C318416840

当前稳定性状况

技术评估上目前稳定性处于Beta版本。由于HybridCLR技术原理的先进性,bug本质上不多,稳定得非常快。已经有一段时间未收到2020.3.33版本的bug反馈。

  • 目前PC、Android、iOS 已跑通所有单元测试,可稳定体验使用。
  • 2022.6.7 第一款使用HybridCLR的android和iOS双端休闲游戏正式上线
  • 2022.7 将有至少2款重度项目和2款中度游戏上线
  • 2022年预计有几十款中重度项目及超过一百款轻度或者独立游戏上线
已经有几十个大中型游戏项目较完整地接入HybridCLR,并且其中一些在紧锣密鼓作上线前测试。具体参见收集的一些 完整接入的商业项目列表
总结

HybridCLR是一个划时代的Unity平台C#原生热更新技术,它将国内Unity开发的技术框架水平提高到新的高度,并深刻地改变Unity平台的开发生态。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2023-6-17 12:06 | 显示全部楼层


https://www.zhihu.com/video/1489931398693306369
安装


  • 安装 2020.3.7f 版本,注意要选中win64下的il2cpp模块(如果你发布其他平台,则选择相应平台的il2cpp模块)。
  • 用huatuo项目的libil2cpp目录替换Editor安装目录下的 Editor/Data/il2cpp/libil2cpp 目录。
至此即完成huatuo安装,后续打包出的app就能支持c#热更新。是不是简单到有点匪夷所思?
项目的准备工作


  • 使用 Unity的 Assembly def 创建一个专门的 HotFix 模块(你也可以使用创建第三方工程的方式,只不过麻烦了点)。
  • 配置HotFix模块

    • 根据需求设置正确的Assembly Definition Reference
    • Platforms 选项下,取消AnyFlatform,接着只选中 Editor和随便一个不导出的平台(如XboxOne)。不能只有Editor,因为Unity会把它当作纯Editor模板,不允许加载Monobehaviour。



  • 将示例项目Main下的 RefTypes.cs 拷贝到 你项目中(非必须,纯粹是为了防止unity裁剪,如果你已经正确设置了link.xml,则可忽略这步)
代码中使用


  • 在HotFix模块中创建第一个热更新脚本,类似如下
public class App
{
    public static int Main()
    {
        Debug.Log("hello,huatuo");
        return 0;
    }
}

  • 主工程中,使用标准反射函数加载Hotfix.dll,以示例 LoadDll.cs 为例
public class LoadDll : MonoBehaviour
{
    void Start()
    {
        LoadGameDll();
        RunMain();
    }

    private System.Reflection.Assembly gameAss;

    private void LoadGameDll()
    {
#if UNITY_EDITOR
        string gameDll = Application.dataPath + "/../Library/ScriptAssemblies/HotFix.dll";
        // 使用File.ReadAllBytes是为了避免Editor下gameDll文件被占用导致后续编译后无法覆盖
        gameAss = System.Reflection.Assembly.Load(File.ReadAllBytes(gameDll));
#else
        string gameDll = Application.streamingAssetsPath + "/HotFix.dll";
        gameAss = System.Reflection.Assembly.LoadFile(gameDll);
#endif
    }

    public void RunMain()
    {
        if (gameAss == null)
        {
            UnityEngine.Debug.LogError("dll未加载");
            return;
        }
        var appType = gameAss.GetType("App");
        var mainMethod = appType.GetMethod("Main");
        mainMethod.Invoke(null, null);
    }
}

  • Build & Run

    • 将 HotFix.dll拷到StreamingAssets下
    • 发布选项设置

      • Scripting Backend 选择 il2cpp backend
      • Api Compatible level 选择 .NET 4.x
      • 取消 use incremental GC





    • 发布
    • 进入场景后,应该能看到日志 "hello,huatuo"



  • 测试热更新
修改HotFix模块中的 App::Main代码。改成
public class App
{
    public static int Main()
    {
        Debug.Log("hello,world");
        return 0;
    }
}将编译后的Hotfix.dll 复制替换发布目标目录下的 StreamingAssets下的Hotfix.dll,重新运行程序。 你应该会看到打印出 "hello,world"。
至此完成。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-5-1 10:45 , Processed in 0.106631 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表