27.ILRuntime基础知识总结

  1. 27.总结
    1. 27.1 知识点
      1. 学习的主要内容
      2. 强调ILRuntime和Lua热更的原理
      3. 我们应该达到的水平
      4. 实践项目
    2. 27.2 核心要点速览
      1. ILRuntime 基础概念与环境搭建
      2. AppDomain —— ILRuntime 的核心入口
      3. Unity 调用 ILRuntime —— 实例化与方法调用
        1. 实例化热更对象(三种方式)
        2. 方法调用三板斧
      4. ILRuntime 调用 Unity
      5. 跨域调用的核心问题与解决方案
        1. 委托跨域
        2. 跨域继承
        3. CLR 绑定与重定向
        4. 值类型绑定
      6. MonoBehaviour、协程、异步函数
        1. MonoBehaviour
        2. 协程与异步
      7. 反射与序列化
        1. 反射
        2. 序列化(LitJson)
      8. 调试相关
      9. 性能优化
        1. 寄存器模式
        2. 性能优化清单
      10. 底层原理
        1. 执行流程
        2. 类型系统
        3. 解释器与运行栈
        4. CLR 重定向原理
    3. 27.3 面试题精选
      1. 基础题
        1. 1. ILRuntime 的基本原理是什么?与 Lua 热更有何区别?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 2. Unity 调用 ILRuntime 中的方法有哪几种方式?性能最优的是哪种?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      2. 进阶题
        1. 3. 什么是 CLR 绑定?为什么要做 CLR 绑定?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 4. 跨域继承有哪些限制?如何解决多继承问题?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 5. 为什么 ILRuntime 不推荐直接继承 MonoBehaviour?应该如何处理?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 6. 如何在 ILRuntime 中使用协程和 async/await?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
      3. 深度题
        1. 7. ILRuntime 性能优化有哪些关键点?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        2. 8. ILRuntime 的底层原理是什么?请结合类型系统和运行栈说明。
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        3. 9. 什么是寄存器模式?什么场景下适合开启?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章
        4. 10. ILRuntime 的调试流程是怎样的?如何在热更代码中设置断点?
          1. 题目
          2. 深入解析
          3. 答题示例
          4. 参考文章

27.总结


27.1 知识点

学习的主要内容

强调ILRuntime和Lua热更的原理

我们应该达到的水平

实践项目


27.2 核心要点速览

ILRuntime 基础概念与环境搭建

ILRuntime 是一套纯 C# 实现的热更新方案,核心原理是利用 Mono.Cecil 库在运行时读取并解释执行 DLL 文件中的 IL 指令。与 Lua 热更不同,ILRuntime 让我们继续用 C# 写热更代码,学习成本更低。

环境搭建要点:

步骤 操作
1 修改 manifest.json,添加 ILRuntime 的 scopedRegistries
2 Package Manager 安装 ILRuntime
3 Project Settings 中勾选 Allow 'unsafe' Code
4 热更工程生成 DLL/PDB 到 StreamingAssets

开发方式: Unity 主工程 + HotFix_Project 热更工程双工程协作,两者可相互调用。

AppDomain —— ILRuntime 的核心入口

AppDomain 是 ILRuntime 的”大脑”,负责加载和解释执行热更 DLL。使用流程:

// 1. 创建 AppDomain
AppDomain appDomain = new AppDomain();

// 2. 加载 DLL 和 PDB(通过内存流)
MemoryStream dllStream = new MemoryStream(dllBytes);
MemoryStream pdbStream = new MemoryStream(pdbBytes);
appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());

// 3. 设置主线程 ID(用于 Profiler 分析)
appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;

// 4. 销毁时关闭流
dllStream.Close();
pdbStream.Close();

注意: 内存流不要用完就关,热更代码会持续访问,应在 AppDomain 销毁时再关闭。

Unity 调用 ILRuntime —— 实例化与方法调用

实例化热更对象(三种方式)

方式 代码示例 推荐度
AppDomain.Instantiate appDomain.Instantiate("HotFix_Project.TestClass") ★★☆
IType + ILType.Instantiate ((ILType)appDomain.LoadedTypes["HotFix_Project.TestClass"]).Instantiate() ★★★
反射构造函数 type.ReflectionType.GetConstructor(...).Invoke(...) ★★☆

推荐方式二,获取 IType 后后续调用成员更方便。

方法调用三板斧

静态方法:

// 方式1:直接调用
appDomain.Invoke("HotFix_Project.TestClass", "StaticMethod", null, null);

// 方式2:IMethod 调用
IMethod method = type.GetMethod("StaticMethod", 0);
appDomain.Invoke(method, null, null);

// 方式3:无 GC Alloc 方式(性能最优)
using (var ctx = appDomain.BeginInvoke(method)) {
    ctx.Invoke();
}

成员方法: 与静态方法类似,区别在于需要传入对象实例。

重载方法: 参数数量不同时用 GetMethod("方法名", 参数个数);参数类型不同时需指定参数类型列表:

List<IType> paramTypes = new List<IType> { appDomain.GetType(typeof(float)) };
IMethod method = type.GetMethod("TestFun", paramTypes, null);

ref/out 参数: 只能用无 GC Alloc 方式,需先压入 ref/out 参数初始值,再压入引用索引:

using (var ctx = appDomain.BeginInvoke(method)) {
    ctx.PushObject(list);      // ref 参数初始值
    ctx.PushObject(null);      // out 参数初始值
    ctx.PushObject(obj);       // 调用对象
    ctx.PushInteger(100);      // 普通参数
    ctx.PushReference(0);      // ref 参数索引
    ctx.PushReference(1);      // out 参数索引
    ctx.Invoke();
    list = ctx.ReadObject<List<int>>(0);  // 读取 ref 参数
    float f = ctx.ReadFloat(1);           // 读取 out 参数
}

属性访问: ILRuntime 无法直接访问字段,需用属性封装。属性方法名为 get_属性名 / set_属性名

ILRuntime 调用 Unity

热更工程调用 Unity 非常简单,引用命名空间后直接使用即可。关键是热更工程需要引用对应的 Unity DLL:

  1. 找到需要的 Unity 模块 DLL(如 UnityEngine.PhysicsModule.dll
  2. 复制到热更工程的 UnityDlls 文件夹
  3. 在热更工程中添加引用
  4. 将 DLL 的”复制本地”设为 false(避免输出到 StreamingAssets)
步骤 操作
1 找到需要的 Unity 模块 DLL(如 UnityEngine.PhysicsModule.dll
2 复制到热更工程的 UnityDlls 文件夹
3 在热更工程中添加引用
4 将 DLL 的”复制本地”设为 false

主入口模式: 在热更工程创建 ILRuntimeMain 类,提供 Start() 静态方法作为程序入口,Unity 端调用后即把控制权交给热更层。

跨域调用的核心问题与解决方案

委托跨域

当 Unity 中的自定义委托需要关联热更工程中的函数时,需要注册:

// 1. 注册委托(避免 IL2CPP 裁剪)
appDomain.DelegateManager.RegisterFunctionDelegate<int, int, int>();

// 2. 注册委托转换器(将自定义委托转为 Action/Func)
appDomain.DelegateManager.RegisterDelegateConvertor<MyDel>((act) => {
    return new MyDel((a, b) => ((Func<int, int, int>)act)(a, b));
});

最佳实践: 尽量使用 Action<T>Func<T>,只需注册委托,无需转换器。

跨域继承

热更工程继承主工程的类需要适配器:

// 1. 生成适配器代码
using (StreamWriter sw = new StreamWriter("路径/XxxAdapter.cs")) {
    sw.WriteLine(CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(
        typeof(父类), "命名空间"));
}

// 2. 注册适配器
appDomain.RegisterCrossBindingAdaptor(new XxxAdapter());

跨域继承限制:

  • 不支持同时继承类和接口(多继承)
  • 如需多继承,在主工程创建一个已实现多继承的中间类
  • 不能在基类构造函数中调用虚函数

CLR 绑定与重定向

CLR 绑定作用:

  • 将反射调用变为直接调用,大幅提升性能
  • 避免 IL2CPP 打包时裁剪有用代码

使用步骤:

  1. ILRuntimeCLRBinding.csInitILRuntime 中注册适配器
  2. 点击菜单 ILRuntime -> 通过自动分析热更DLL生成CLR绑定
  3. 在初始化时调用 CLRBindings.Initialize(appDomain)

自定义 CLR 重定向: 可以”劫持”方法调用,比如给 Debug.Log 加上行号信息:

MethodInfo logMethod = typeof(Debug).GetMethod("Log", new Type[] { typeof(object) });
appDomain.RegisterCLRMethodRedirection(logMethod, MyLog);

// 重定向方法实现
unsafe static StackObject* MyLog(ILIntepreter intp, StackObject* esp, IList<object> mStack, 
    CLRMethod method, bool isNewObj) {
    // 获取调用参数
    object arg = MiniILIntepreter.CheckAndFetchReferencedObject(intp, esp, mStack);
    // 添加行号信息
    Debug.Log($"[Line {intp.CurrentFrame.Caller.LineNumber}] {arg}");
    return esp;
}

值类型绑定

appDomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
appDomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
appDomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());

MonoBehaviour、协程、异步函数

MonoBehaviour

不推荐跨域继承 MonoBehaviour,底层由 C++ 处理,热更类无法被正确识别。

推荐做法: 在主工程创建代理脚本,通过事件派发生命周期:

public class ILRuntimeMono : MonoBehaviour {
    public event Action startEvent;
    public event Action updateEvent;
    void Start() => startEvent?.Invoke();
    void Update() => updateEvent?.Invoke();
}

协程与异步

// 协程适配器(示例工程自带)
appDomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());

// 异步函数适配器
appDomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());

反射与序列化

反射

IType iType = appDomain.LoadedTypes["HotFix_Project.TestClass"];
Type type = iType.ReflectionType;
// 用标准反射 API 操作

注意: 不能用 Activator.CreateInstance(type) 创建热更对象。

序列化(LitJson)

JsonMapper.RegisterILRuntimeCLRRedirection(appDomain);
// 之后正常使用 ToJson / ToObject<T>

调试相关

ILRuntime 支持断点调试,需要安装 IDE 调试插件:

IDE 插件名称
Visual Studio ILRuntime Debugger(扩展管理器搜索)
Rider ILRuntime Debugger(插件市场搜索)

调试配置:

// 1. 启动调试服务(开发模式)
appDomain.DebugService.StartDebugService(56000);

// 2. 等待调试器附加
while (!appDomain.DebugService.IsDebuggerAttached) {
    yield return null;
}

调试流程: 运行 Unity → IDE 选择 Attach to ILRuntime → 输入 IP 和端口 → 设置断点

注意: 发布版本应关闭调试服务,不加载 PDB 文件。

性能优化

寄存器模式

ILRuntime 2.0 引入的优化机制,通过 JIT 编译提升数值计算性能。

开启方式:

// 全局开启(推荐)
appDomain = new AppDomain(ILRuntimeJITFlags.JITOnDemand);

// 局部指定
[ILRuntimeJIT(ILRuntimeJITFlags.JITImmediately)]
class MyClass { }
模式 说明
None 不启用
JITOnDemand 按需编译,方法被反复执行时自动 JIT
JITImmediately 立即编译,首次执行即用寄存器模式
NoJIT 禁用,始终用传统方式

性能优化清单

类别 优化项
编译设置 DLL 用 Release 编译
打包设置 不勾选 Development Build;IL2CPP 用 Release 配置
CLR 绑定 必须生成并注册 CLR 绑定
值类型 注册 Vector3/Vector2/Quaternion 绑定
代码习惯 避免频繁 foreach;高频调用用无 GC Alloc 方式
架构设计 耗时算法、稳定模块放主工程
寄存器模式 计算密集型代码开启
预热 appDomain.Prewarm() 减少首次执行开销
发布优化 不加载 PDB;关闭调试服务

底层原理

执行流程

C# 源码 → 编译器 → DLL(IL 中间语言)
                        ↓
                 Mono.Cecil 读取
                        ↓
                 ILRuntime 解释器
                        ↓
                 类型系统 + 运行栈
                        ↓
                    执行代码

类型系统

类型 说明
IType 类型基类接口
ILType 热更工程中的类型
CLRType 主工程中的类型
ILMethod 热更方法
CLRMethod 主工程方法
ILTypeInstance 热更对象实例

解释器与运行栈

ILRuntime 使用非托管内存实现运行栈,通过指针直接操作内存:

栈结构:
┌─────────────┐
│  参数1      │
│  参数2      │
│  局部变量1  │
│  栈指针 ──→ │
└─────────────┘

方法调用流程: 参数压栈 → 移动指针获取参数 → 执行方法体 → 清理栈

CLR 重定向原理

当热更代码调用主工程方法时:解释器遇到调用指令 → 检查是否有重定向注册 → 有则调用委托函数,无则用反射。


27.3 面试题精选

基础题

1. ILRuntime 的基本原理是什么?与 Lua 热更有何区别?

题目

请简述 ILRuntime 的基本原理,并与 Lua 热更方案进行对比。

深入解析

ILRuntime 利用 Mono.Cecil 库在运行时读取 DLL 文件中的 IL 指令,通过解释器逐条执行。核心流程:DLL 加载 → IL 指令解析 → 解释执行。

与 Lua 热更的对比:

对比项 ILRuntime Lua 热更
开发语言 C# Lua
学习成本 低(Unity 开发者友好) 中(需学习 Lua 语法)
类型安全 编译时检查 运行时检查
调试支持 较好(可断点调试) 一般
性能 中等(解释执行) 较好(LuaJIT)
跨域调用 需要适配器/绑定 相对简单
答题示例

ILRuntime 利用 Mono.Cecil 库在运行时读取 DLL 中的 IL 指令,通过解释器逐条执行。

与 Lua 热更相比,ILRuntime 使用 C# 开发,对 Unity 开发者学习成本低,有编译时类型检查和较好的断点调试支持。但性能略逊于 LuaJIT,跨域调用需要适配器和绑定。

参考文章
  • 1.概述
  • 23.原理相关-基本原理

2. Unity 调用 ILRuntime 中的方法有哪几种方式?性能最优的是哪种?

题目

Unity 调用 ILRuntime 热更方法有哪些方式?哪种性能最优?

深入解析

三种调用方式:

  1. 直接调用: appDomain.Invoke("命名空间.类名", "方法名", 对象, 参数)
  2. IMethod 调用: 先获取 IMethod,再通过 appDomain.Invoke(method, ...)
  3. 无 GC Alloc 方式: 使用 BeginInvokePushInvokeRead 链式调用
// 无 GC Alloc 方式示例
using (var ctx = appDomain.BeginInvoke(method)) {
    ctx.PushObject(obj);
    ctx.PushInteger(100);
    ctx.Invoke();
    int result = ctx.ReadInteger(0);
}

性能最优的是第三种,因为它避免了反射调用产生的 GC 分配。

答题示例

有三种方式:直接调用、IMethod 调用、无 GC Alloc 方式。

性能最优的是无 GC Alloc 方式,使用 BeginInvoke/Push/Invoke/Read 链式调用,避免了反射产生的 GC 分配。实际项目中,高频调用的方法建议用这种方式,低频调用可用更简洁的直接调用。

参考文章
  • 5.调用ILRuntime中的方法
  • 6.调用ILRuntime中的方法-重载方法
  • 7.调用ILRuntime中的方法-ref和out参数

进阶题

3. 什么是 CLR 绑定?为什么要做 CLR 绑定?

题目

解释 CLR 绑定的概念和作用,以及使用时机。

深入解析

CLR 绑定是 ILRuntime 提供的性能优化机制,本质是利用 CLR 重定向,将原本需要反射调用的方法”绑定”到直接调用。

两个核心作用:

  1. 性能提升: 将反射调用变为直接调用,性能可提升数倍甚至数十倍
  2. 避免裁剪: IL2CPP 打包时会裁剪”未使用”的代码,CLR 绑定可保留热更工程用到的主工程代码

使用步骤:

  1. ILRuntimeCLRBinding.cs 中注册适配器
  2. 点击菜单 ILRuntime -> 通过自动分析热更DLL生成CLR绑定
  3. 初始化时调用 CLRBindings.Initialize(appDomain)

使用时机: 每次打包发布前都应重新生成 CLR 绑定代码。

答题示例

CLR 绑定是 ILRuntime 的性能优化机制,通过重定向将反射调用变为直接调用。

两个核心作用:一是大幅提升性能,反射调用开销大,绑定后可提升数倍;二是避免 IL2CPP 代码裁剪,保留热更工程用到的主工程代码。

使用时机是每次打包发布前重新生成绑定代码。

参考文章
  • 14.更多跨域调用-CLR绑定
  • 15.更多跨域调用-CLR重定向

4. 跨域继承有哪些限制?如何解决多继承问题?

题目

ILRuntime 跨域继承有什么限制?如何解决同时继承类和接口的问题?

深入解析

限制:

  1. 不支持同时继承类和实现接口
  2. 不能在基类构造函数中调用虚函数(会报空引用)

原因: 跨域继承实际上是继承适配器类,而适配器类只能继承一个类,C# 不支持多继承。

多继承解决方案: 在主工程创建一个”中间类”实现多继承,热更类再继承这个中间类:

// 主工程
public abstract class MiddleClass : BaseClass, IInterface { }

// 热更工程
public class HotfixClass : MiddleClass { }
答题示例

跨域继承有两个限制:不支持同时继承类和实现接口;不能在基类构造函数中调用虚函数。

原因是跨域继承实际继承的是适配器类,而适配器类只能单继承。

解决多继承问题的方法是在主工程创建中间类,让中间类先实现多继承,热更类再继承中间类。

参考文章
  • 12.更多跨域调用-跨域继承
  • 13.更多跨域调用-跨域继承补充

5. 为什么 ILRuntime 不推荐直接继承 MonoBehaviour?应该如何处理?

题目

为什么热更工程中不建议直接继承 MonoBehaviour?正确的做法是什么?

深入解析

原因: MonoBehaviour 是特殊类,很多底层逻辑由 C++ 处理(如序列化、Inspector 显示、生命周期回调),热更工程中的 C# 类无法被底层 C++ 正确识别。

推荐方案: 使用事件派发模式,在主工程创建代理脚本:

public class ILRuntimeMono : MonoBehaviour {
    public event Action awakeEvent;
    public event Action startEvent;
    public event Action updateEvent;
    public event Action destroyEvent;
    
    void Awake() => awakeEvent?.Invoke();
    void Start() => startEvent?.Invoke();
    void Update() => updateEvent?.Invoke();
    void OnDestroy() => destroyEvent?.Invoke();
}

热更工程通过注册事件来响应生命周期,与 Lua 热更中的做法类似。

答题示例

不推荐的原因是 MonoBehaviour 的底层逻辑由 C++ 处理,热更类无法被正确识别,会导致序列化、Inspector 显示等功能异常。

正确做法是使用事件派发模式:在主工程创建代理脚本,通过事件暴露生命周期回调,热更工程注册事件来响应。

参考文章
  • 16.更多跨域调用-MonoBehaviour在热更工程中使用

6. 如何在 ILRuntime 中使用协程和 async/await?

题目

ILRuntime 中如何正确使用协程和异步函数?

深入解析

协程和异步函数编译后会生成状态机类,这些类涉及跨域继承,需要注册适配器:

// 协程适配器(ILRuntime 示例工程自带)
appDomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());

// 异步函数适配器(需单独获取)
appDomain.RegisterCrossBindingAdaptor(new IAsyncStateMachineClassInheritanceAdaptor());

注册后即可在热更工程中正常使用:

// 协程
public IEnumerator MyCoroutine() {
    yield return new WaitForSeconds(1f);
    Debug.Log("1秒后执行");
}

// 异步
public async void MyAsync() {
    await Task.Delay(1000);
    Debug.Log("1秒后执行");
}
答题示例

协程和异步函数编译后会生成状态机类,涉及跨域继承,需要先注册适配器。

协程适配器 CoroutineAdapter 在示例工程中自带,异步适配器 IAsyncStateMachineClassInheritanceAdaptor 需单独获取。注册后即可在热更工程中正常使用协程和 async/await。

参考文章
  • 17.更多跨域调用-协同程序和异步函数

深度题

7. ILRuntime 性能优化有哪些关键点?

题目

请全面阐述 ILRuntime 性能优化的关键点,包括设置、代码和架构层面。

深入解析

设置相关:

  • 热更 DLL 使用 Release 编译
  • 打包时不勾选 Development Build
  • IL2CPP 打包时 C++ Compiler Configuration 设为 Release

代码相关:

  • 必须进行 CLR 绑定和值类型绑定
  • 避免在热更代码中频繁使用 foreach(会产生 GC)
  • 热更调用主工程开销 < 主工程调用热更开销
  • 主工程调用热更时优先使用无 GC Alloc 方式
  • 合理规划热更/非热更边界,消耗大的算法放主工程

可选优化:

  • 使用 appDomain.Prewarm() 预热方法,减少首次执行开销
  • 发布时不加载 PDB 文件节省内存
  • 对计算密集型代码开启寄存器模式

寄存器模式选择:

场景 推荐模式
大量数值计算 JITOnDemand 或 JITImmediately
纯 API 调用 NoJIT 或不开启
混合场景 JITOnDemand(全局)
答题示例

ILRuntime 性能优化分三个层面:

设置层面:DLL 用 Release 编译,打包不勾选 Development Build,IL2CPP 用 Release 配置。

代码层面:必须做 CLR 绑定和值类型绑定;避免频繁 foreach;高频调用用无 GC Alloc 方式。

架构层面:耗时算法和稳定模块放主工程,热更只放业务逻辑;计算密集型代码开启寄存器模式。

发布时可选择性不加载 PDB、用 Prewarm 预热方法来进一步优化。

参考文章
  • 21.性能相关-寄存器模式
  • 22.性能相关-性能优化相关

8. ILRuntime 的底层原理是什么?请结合类型系统和运行栈说明。

题目

请详细阐述 ILRuntime 的底层原理,包括执行流程、类型系统和运行栈机制。

深入解析

核心执行流程:

  1. Mono.Cecil 读取 DLL: 获取类型、方法、IL 指令等元信息
  2. 类型系统转换: 将 IL 代码翻译为 ILRuntime 内部类型
  3. 解释器执行: 遍历 IL 指令,通过 switch-case 处理每条指令
  4. 运行栈管理: 使用非托管内存,通过 StackObject 和栈指针操作数据

类型系统:

类型 说明
IType 类型基类接口
ILType 热更工程中的类型
CLRType 主工程中的类型
ILTypeInstance 热更对象实例

运行栈机制:

ILRuntime 使用非托管内存实现运行栈,通过指针直接操作内存。方法调用时参数依次压栈,通过移动栈指针获取参数,执行完毕后移动指针清理栈。

CLR 重定向原理:

当热更代码调用主工程方法时,解释器遇到调用指令后检查是否有重定向注册。有重定向则调用绑定的委托函数(直接调用),无则使用反射调用。这就是 CLR 绑定能大幅提升性能的原因。

答题示例

ILRuntime 底层是一个 C# 实现的虚拟机:

执行流程:Mono.Cecil 读取 DLL 获取 IL 指令 → 类型系统转换为内部类型 → 解释器逐条执行。

类型系统:ILType 表示热更类型,CLRType 表示主工程类型,ILTypeInstance 是热更对象实例的载体。

运行栈:使用非托管内存,通过 StackObject 和栈指针操作数据,方法调用时参数压栈、执行后移动指针清理。

重定向:热更调用主工程方法时,有重定向则直接调用委托,无则反射调用,这就是 CLR 绑定能提升性能的本质。

参考文章
  • 23.原理相关-基本原理
  • 24.原理相关-类型系统
  • 25.原理相关-解释器
  • 26.原理相关-重定向的书写规则

9. 什么是寄存器模式?什么场景下适合开启?

题目

解释 ILRuntime 寄存器模式的原理,以及什么场景适合开启、什么场景不适合。

深入解析

寄存器模式是 ILRuntime 2.0 引入的优化机制,通过 JIT 编译将 MSIL 转换为基于寄存器的自定义指令集,显著提升数值计算性能。

原理: 传统解释器基于栈操作,每次运算都要压栈出栈。寄存器模式将操作转换为寄存器直接操作,减少内存访问次数。

适合开启的场景:

  • 大量数值计算(for 循环、数学运算)
  • 频繁访问热更内属性或方法

不适合开启的场景:

  • 纯 API 调用(无计算逻辑)
  • 主要调用 Unity/系统 API

模式选择:

模式 适用场景
JITOnDemand 通用场景,让 ILRuntime 自动判断
JITImmediately 明确知道某方法需要高性能
NoJIT 纯 API 调用的方法

推荐做法: 全局使用 JITOnDemand 模式,让 ILRuntime 自动判断;或通过 [ILRuntimeJIT] 特性精准控制特定类或方法。

答题示例

寄存器模式是 ILRuntime 2.0 的优化机制,通过 JIT 将 MSIL 转换为基于寄存器的指令集,减少栈操作开销。

适合场景:大量数值计算、频繁访问热更内属性或方法。

不适合场景:纯 API 调用、主要调用 Unity/系统 API。

推荐做法:全局用 JITOnDemand 让 ILRuntime 自动判断,或用特性精准控制特定方法。

参考文章
  • 21.性能相关-寄存器模式

10. ILRuntime 的调试流程是怎样的?如何在热更代码中设置断点?

题目

请描述 ILRuntime 的完整调试流程,以及调试移动设备的方案。

深入解析

调试流程:

  1. 安装插件: VS/Rider 安装 ILRuntime Debugger 插件
  2. 启动调试服务: appDomain.DebugService.StartDebugService(端口号)
  3. 等待附加: 用协程等待 IsDebuggerAttached 变为 true
  4. 附加调试器: IDE 中选择 Attach to ILRuntime,输入 IP 和端口
  5. 设置断点: 在热更代码中设置断点
  6. 开始调试: 正常使用断点、单步执行等功能

调试服务配置:

// 开发模式下启动调试服务
if (isDebug) {
    appDomain.DebugService.StartDebugService(56000);
}

// 等待调试器附加
IEnumerator WaitDebugger(UnityAction callback) {
    while (!appDomain.DebugService.IsDebuggerAttached) {
        yield return null;
    }
    callback?.Invoke();
}

移动设备调试:

  • 确保设备与开发机在同一网络
  • 使用设备的 IP 地址连接
  • 端口号需与启动服务时一致

注意事项:

  • 开发模式才启用调试服务
  • 发布版本不加载 PDB、关闭调试服务
  • 可通过 IP + 端口调试移动设备
答题示例

ILRuntime 调试流程:安装 IDE 插件 → 启动调试服务 → 等待调试器附加 → 在 IDE 中 Attach to ILRuntime → 设置断点调试。

关键点:需要用协程等待 IsDebuggerAttached 变为 true 后再执行主逻辑,否则启动时的代码无法断点。

移动设备调试:使用设备 IP 地址连接,端口与启动服务时一致。发布时记得关闭调试服务、不加载 PDB。

参考文章
  • 20.调试相关


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏