You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于C#/Mono.Cecil确定方法所有退出指令的技术问询

在Unity中实现类似PostSharp OnExit的IL织入逻辑

核心问题拆解

你遇到的本质问题是:常规的OpCodes.Ret、OpCodes.Throw/OpCodes.Rethrow只能覆盖显式返回和主动抛异常的场景,但函数还会通过异常传播、协程状态机切换、迭代器遍历结束这些隐式路径退出,这些场景不会触发上述指令,导致OnExit逻辑遗漏。

分场景IL指令处理方案

针对不同的函数退出路径,需要分别处理对应的IL结构:

1. 显式返回路径:处理所有OpCodes.Ret

这是最基础的场景,遍历方法的IL指令序列,在每一条OpCodes.Ret指令前插入OnExit逻辑。注意要保证插入的指令不破坏原栈的状态——比如如果原Ret指令前有返回值在栈上,插入的OnExit方法不能消耗或修改这个值。

2. 异常传播路径:分析Exception Handler块

当函数内部调用的方法抛出异常且未被本地catch捕获时,函数会直接跳转到上层异常处理逻辑,此时需要:

  • 遍历方法的ExceptionHandlers集合,针对每个Catch、Finally、Fault类型的Handler:
    • 找到Handler块内的所有退出点:包括块内的Ret、Throw,以及跳转到块外的分支指令(比如OpCodes.Br、OpCodes.Brtrue等跳转到Handler范围外的指令)
    • 在这些退出点前插入OnExit逻辑
  • 特别注意Finally块:无论是否发生异常,Finally块都会执行,所以必须在Finally块的所有退出点插入逻辑,保证异常场景下OnExit也能触发。

3. 协程(Yield Return):处理状态机的MoveNext方法

Unity协程本质是编译器生成的状态机类(带[CompilerGenerated]属性,实现IEnumerator),原方法的逻辑会被拆分到状态机的MoveNext方法中。需要:

  • 识别目标方法对应的状态机类(可通过方法的CustomAttributes或查找引用该方法的编译器生成类)
  • 找到状态机类的MoveNext方法,对该方法执行和普通方法一样的处理:遍历所有Ret指令、分析异常Handler块,插入OnExit逻辑
  • 注意:协程的yield return会触发状态机暂停,此时不算函数退出,只有当MoveNext返回false(协程结束)时的Ret才是真正的退出点,不过保险起见可以在MoveNext的所有Ret前都插入逻辑,或者判断返回值是否为false后再执行。

4. 迭代器(foreach):处理迭代器状态机

foreach依赖的迭代器同样是编译器生成的状态机(实现IEnumerator/IEnumerable),处理逻辑和协程一致:

  • 找到编译器生成的状态机类及其MoveNext方法
  • 在MoveNext方法的所有Ret和异常Handler块退出点插入OnExit逻辑

基于Mono.Cecil的实现要点

  1. 保留栈状态:插入OnExit方法调用时,使用OpCodes.Call(而非Callvirt,如果OnExit是静态方法),且确保调用前后栈的元素数量和类型不变——比如如果原指令栈顶是返回值,调用OnExit后要把这个值重新放回栈顶。
  2. 避免循环处理:处理状态机方法时,要标记已处理的方法,避免重复织入。
  3. 兼容性:Unity使用的Mono版本可能有差异,测试时要覆盖不同Unity版本(比如2020+、2022+)的IL生成逻辑。

简化方案:复用现有库扩展

如果不想手动实现完整的IL分析,可以基于你找到的MewWeaver进行扩展:

  • 新增对Exception Handler块的遍历逻辑
  • 添加状态机方法的自动识别和处理逻辑
  • 整合所有退出点的织入逻辑

内容的提问来源于stack exchange,提问作者necrokamios

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 02:25:04