1. Editor 下做的事 和 打包时做的事
- 首先把 il2cpp 的库稍作修改,让其支持 il2cppCLR
- 把 il2cppCLR 添加到 libil2cpp 库里面
- 拆分热更 DLL、AOT DLL
- 根据热更 DLL、AOT DLL 生成对应的需要补充元数据的 DLL、以及 link.xml
- 为 HybridCLR 中的热更 C++ 代码调用 AOT 代码生成桥接文件,因为 ABI 都一样,所以避免了
注意点:
生成需要补充元数据的 DLL 的原理就是对比热更 DLL 与 AOT DLL,如果热更 DLL 中调用了 AOT DLL 中的类型,则需要补充。AOT DLL 是 il2cpp.exe 执行的产物,所以 Android 上是进行 Export,相当于进行了一次打包,大型项目耗时,可以先进行 Export,再打包,一次性走完。
1.2 打包时做的事
2.1 首先 C# 代码要转换成 IL,这一步是由 Roslyn 编译器执行。然后通过 il2cpp.exe 把 IL 转换成 C++ 代码,然后交由 C++ 编译器进行 AOT 机器码生成,比如 Clang,输出 libil2cpp.so 库。这里的 C# 代码包括 AOT DLL 和热更 DLL,为后边 DHE 的工作做了铺垫,因为热更 DLL 也有 AOT 机器码。
2.2 在转换过程中,根据代码可达性分析,进行裁剪(这里指的是把 IL 代码进行裁剪,但是基本的元数据还在。比如类型、方法名、参数名等),输出一份元数据文件 global-metadata.dat 二进制文件。

2. 运行时做的事
2.1 运行时序图

2.2 运行时步骤:
2.2.1
il2cpp 读取 global-metadata.dat 文件,把方法注册进缓存表里面,同时根据方法的可达性即:有没有被裁剪或者没被裁剪,但是不可触达,把对应的 methodpoint 指针设置为 null 或者 AOT 指针。
2.2.2
HybridCLR 读取热更 DLL,为每个热更方法,修改对应的 methodpoint,把 global-metadata 里面已经有的,methodpoint 标记为 AOT 指针,把热更里面方法,修改成 stub 方法指针,同时为方法添加一个 methodindex 变量,方便后续调用时查找。
2.2.3
HybridCLR 读取补充元数据 DLL,把泛型信息,重新注册进泛型表。让调用可触达
2.2.4
HybridCLR 读取 DHE 数据,把热更方法里面 AOT 里面已经有的,methodpoint 走 AOT,而不是走 stub 函数。
2.2.5
为纯热更方法生成对应的 IR 寄存器数组(模拟 CPU 指令集,同时增加了专有类型的指令,比如 iadd、fadd),以 methodindex 做 key。
3. 调用方法时做的事
输入方法名和方法参数,通过反射机制获取方法信息。HybridCLR 查找 medatacache 表,找到对应的 methodpoint。如果是 AOT 类型,则直接执行;如果是 stub 函数,则执行 stub 函数。stub 函数如果 DHE 缓存表中存在,则走 AOT 路径;如果不存在,则走缓存表进行解释执行,输入 IL 指令。如果 IL 也不存在,则执行失败,因此 AOT 和 IL 必须至少有一个可用。
4. 补充元数据
4.1 什么是补充元数据?
补充元数据,即把裁剪掉的泛型信息,重新构造并注册进来,让 HybridCLR 运行时能找到
4.2 它的原理是什么?
在创建热更新类型的泛型时,从补充元数据的 DLL 中获取泛型结构信息,从热更 DLL 中获取热更新类型信息,构造出 il2cpp class,注册到内存中,这样 HybridCLR 能够找到其方法,并进行。实例方法,比如 add、remove 通过 inflate 构造,解释器运行。

4.3 完全泛型共享是什么?

完全泛型共享,是值类型和引用类型,都是通过指针类型引用共享一套入口,共享一套 AOT 代码,在运行时,实例化泛型时,根据 T 类型,进行偏移计算。节省了代码体积。
4.4 非完全泛型共享 与 泛型共享 之间的区别
5. DHE 查分混合
因为第一次打包的时候,il2cpp.exe 会为所有的 DLL 生成机器码,包括热更 DLL,只不过没有显示调用的,标记为不可达,在 HybridCLR 加载热更 DLL 的时候,把不可达的方法,统一走解释器,也就是 stub 函数,DHE 是把 AOT 里面有的热更方法,还原成 AOT,不走解释器。
6. Hybrid 的 IR 寄存器和 CPU 物理寄存器的区别


7. Mono 和 il2cpp 的区别

8. 相比较lua、liruntime的区别
HybridCLR 与 Lua、ILRuntime 相比,核心优势在于它并非一门新的脚本语言,而是基于原生 C# 的运行时热更方案,因此在开发体验、性能、生态兼容性等方面有显著不同:
- 没有内存 Marshaling,只是数据转移:Lua 和 ILRuntime 在 C# 与脚本层之间交互时,需要进行类型转换和内存封送(Marshaling),例如将 C# 的 int 转为 Lua 的 number,或将 C# 对象包装为脚本可识别的引用。而 HybridCLR 直接执行原生 IL 指令,热更代码与 AOT 代码共享同一套运行时环境,数据在内存中的布局完全一致,无需任何转换开销,调用效率接近原生。
- 开发语言一致,无需学习新语法:Lua 要求团队额外掌握一门动态语言,ILRuntime 虽然使用 C# 但存在诸多限制(如不支持泛型完整特性、反射受限等)。HybridCLR 完全兼容 C# 标准语法和 .NET API,热更代码可以像普通 C# 一样使用泛型、LINQ、async/await、值类型、ref/out 参数等特性,无需编写适配层或桥接代码。
- 调试体验与原生一致:Lua 和 ILRuntime 的调试需要依赖各自的调试器(如 EmmyLua、ILRuntime 调试插件),断点、变量查看、调用栈等体验与原生 C# 调试存在差距。HybridCLR 的热更代码在运行时仍然是标准的 .NET IL,可以直接使用 Visual Studio、Rider 等 IDE 的 C# 调试器进行断点调试,查看变量值和调用堆栈,大幅降低排查问题的成本。
- 性能优势明显:Lua 基于解释执行(或 JIT),数值运算和对象访问存在额外开销;ILRuntime 通过 CLR 绑定和值类型优化来提升性能,但在复杂泛型、频繁方法调用场景下仍有性能损耗。HybridCLR 的热更方法在首次调用后,解释执行的 IL 指令会被缓存,且配合 DHE(差分混合执行)机制,可将 AOT 阶段已编译的热更方法直接还原为机器码执行,性能接近纯 AOT 代码。
- 代码体积与包体影响小:Lua 需要将热更逻辑以 .lua 文件形式打包或下载,运行时需内置 Lua 虚拟机(约 200KB+);ILRuntime 需要内置 ILRuntime 运行时库。HybridCLR 本身作为 il2cpp 的扩展集成在 libil2cpp 中,热更 DLL 以标准 .NET 程序集形式分发,无需额外虚拟机,对包体增量极小。
- 热更范围更广:Lua 和 ILRuntime 通常只能热更业务逻辑层,难以热更底层框架、第三方 SDK 封装或涉及 AOT 泛型实例化的代码。HybridCLR 通过补充元数据机制,可以热更包含泛型、值类型、P/Invoke、甚至部分 unsafe 代码的 C# 程序集,热更边界更接近完整 C# 开发。