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:
- 找到需要的 Unity 模块 DLL(如
UnityEngine.PhysicsModule.dll) - 复制到热更工程的
UnityDlls文件夹 - 在热更工程中添加引用
- 将 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 打包时裁剪有用代码
使用步骤:
- 在
ILRuntimeCLRBinding.cs的InitILRuntime中注册适配器 - 点击菜单
ILRuntime -> 通过自动分析热更DLL生成CLR绑定 - 在初始化时调用
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 热更方法有哪些方式?哪种性能最优?
深入解析
三种调用方式:
- 直接调用:
appDomain.Invoke("命名空间.类名", "方法名", 对象, 参数) - IMethod 调用: 先获取
IMethod,再通过appDomain.Invoke(method, ...) - 无 GC Alloc 方式: 使用
BeginInvoke→Push→Invoke→Read链式调用
// 无 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 重定向,将原本需要反射调用的方法”绑定”到直接调用。
两个核心作用:
- 性能提升: 将反射调用变为直接调用,性能可提升数倍甚至数十倍
- 避免裁剪: IL2CPP 打包时会裁剪”未使用”的代码,CLR 绑定可保留热更工程用到的主工程代码
使用步骤:
- 在
ILRuntimeCLRBinding.cs中注册适配器 - 点击菜单
ILRuntime -> 通过自动分析热更DLL生成CLR绑定 - 初始化时调用
CLRBindings.Initialize(appDomain)
使用时机: 每次打包发布前都应重新生成 CLR 绑定代码。
答题示例
CLR 绑定是 ILRuntime 的性能优化机制,通过重定向将反射调用变为直接调用。
两个核心作用:一是大幅提升性能,反射调用开销大,绑定后可提升数倍;二是避免 IL2CPP 代码裁剪,保留热更工程用到的主工程代码。
使用时机是每次打包发布前重新生成绑定代码。
参考文章
- 14.更多跨域调用-CLR绑定
- 15.更多跨域调用-CLR重定向
4. 跨域继承有哪些限制?如何解决多继承问题?
题目
ILRuntime 跨域继承有什么限制?如何解决同时继承类和接口的问题?
深入解析
限制:
- 不支持同时继承类和实现接口
- 不能在基类构造函数中调用虚函数(会报空引用)
原因: 跨域继承实际上是继承适配器类,而适配器类只能继承一个类,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 的底层原理,包括执行流程、类型系统和运行栈机制。
深入解析
核心执行流程:
- Mono.Cecil 读取 DLL: 获取类型、方法、IL 指令等元信息
- 类型系统转换: 将 IL 代码翻译为 ILRuntime 内部类型
- 解释器执行: 遍历 IL 指令,通过 switch-case 处理每条指令
- 运行栈管理: 使用非托管内存,通过 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 的完整调试流程,以及调试移动设备的方案。
深入解析
调试流程:
- 安装插件: VS/Rider 安装 ILRuntime Debugger 插件
- 启动调试服务:
appDomain.DebugService.StartDebugService(端口号) - 等待附加: 用协程等待
IsDebuggerAttached变为 true - 附加调试器: IDE 中选择 Attach to ILRuntime,输入 IP 和端口
- 设置断点: 在热更代码中设置断点
- 开始调试: 正常使用断点、单步执行等功能
调试服务配置:
// 开发模式下启动调试服务
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