跳转至

ILRuntime

它解决什么问题

Unity 在 iOS、主机等平台通常使用 IL2CPP,把 C# IL 提前编译为 C++ 与机器码。应用发布后,原生代码不能像脚本一样任意替换,因此需要一种在运行时加载并执行热更程序集的机制。

ILRuntime 用纯 C# 实现了一个 IL 解释器与运行时环境,使热更 DLL 能在不依赖 JIT 的平台上执行。它维护自己的类型系统、堆栈帧和调用约定,并通过绑定层与 Unity 主工程交互。1

热更 DLL
  → ILRuntime 加载程序集
  → 解析类型与方法
  → 解释执行 IL 指令
  → 通过 CLR 绑定调用 Unity / AOT 代码

两个域

域 内容 特点
主工程 AOT 域 Unity 引擎、基础库、稳定框架 编译后不可替换
热更域 业务逻辑、活动、UI 流程 由 ILRuntime 解释执行

跨域调用是性能与复杂度的核心。每一次热更代码访问 AOT 类型,都需要经过绑定与适配。高频路径如果不做绑定优化,开销会非常明显。

常用优化手段

CLR 绑定

为常用 AOT 类型生成绑定代码,减少反射与动态查找。适合:

  • Transform、GameObject 等高频 Unity API;
  • 热更层频繁调用的框架接口;
  • 值类型与泛型方法;
  • 事件、委托与跨域继承。

寄存器模式

ILRuntime 2.x 引入寄存器式执行模式,可减少部分数学与局部变量操作的解释开销。实际收益取决于代码形态,仍需以 Profiler 为准。

减少跨域热点

  • 不要把逐帧逻辑拆成大量细碎跨域调用;
  • 批量传递数据,而不是每项调用一次;
  • 在 AOT 侧提供更粗粒度的接口;
  • 避免在 Update 中频繁创建热更委托;
  • 对热更层做独立性能采样,而不是只看总帧率。

泛型与 AOT 元数据

泛型是热更新方案最容易出问题的区域之一。AOT 编译器会为值类型泛型生成具体实现,但热更程序集在运行时才出现,可能找不到对应实例化。

常见处理:

  • 在 AOT 侧显式引用热更代码会用到的泛型组合;
  • 使用 link.xml 保护被裁剪的类型与成员;
  • 为常用泛型接口生成适配器;
  • 在构建后扫描热更程序集,检查缺失的泛型实例化。

编辑器能跑不代表真机能跑

编辑器可能使用 Mono JIT,路径与 IL2CPP 完全不同。热更代码必须在目标平台的 Development Build 上验证程序集加载、泛型、反射与裁剪。

跨域继承与适配器

热更层继承 AOT 类型或实现 AOT 接口时,需要生成适配器,把调用转发到热更实现。适配器是功能边界,也是性能边界;应避免在每帧路径中大量创建适配器对象。

发布流程

  1. 主工程与热更程序集使用兼容的 API 基线;
  2. 热更 DLL 进入内容包,并携带程序集哈希与版本号;
  3. 启动时校验版本、依赖与 AOT 元数据;
  4. 加载失败时回退到内置稳定程序集;
  5. 线上记录加载耗时、解释器异常与跨域调用错误;
  6. 保留上一版本内容,支持快速回滚。

取舍

ILRuntime 的优势是纯 C#、方案完整、工具链成熟;代价是解释执行性能、跨域绑定复杂度以及泛型与裁剪问题。对于逻辑复杂、热更频率高但性能敏感路径较集中的项目,它仍然是一个可维护的选项。

完整的加载顺序、裁剪、序列化和回滚问题,见热更新常见问题与排查。