Unity集成InjectFix对C#代码热修复


InjectFix介绍

插件地址:https://github.com/Tencent/InjectFix

作用: 用于修复线上C# bug

流程: InjectFix包括Inject和Fix两个部分,发包的时候对需要修复的类进行插桩(Inject)处理,线上版本要写好加载热更补丁包的逻辑,通过生成Patch补丁进行资源热更的形式进行修复线上bug。

原理: InjectFix实现bug修复主要靠两部分:虚拟机负责新逻辑的解析执行;注入代码负责把调用重定向到虚拟机。
作者对Inject的介绍:https://segmentfault.com/a/1190000020375313

优势: 代码都以原生的方式执行,只有需要修复的代码会重定向到虚拟机执行,内部使用反射,不如xlua的静态warp,但考虑到只有少量函数需要修复,不会有大量性能损耗。


InjectFix使用说明

  1. InjectFix通过标签标注的形式,进行对代码插桩和生成补丁包生成处理。
  2. 标签分为注入和补丁。注入标签在发包前做。补丁标签在发包后修复阶段使用。
标签使用阶段用途用法
[Patch]补丁修复函数只能放在函数上
[Interpret]补丁新增属性,函数,类型放在属性,函数,类型上
[CustomBridge]注入interface和delegate桥接只能放在单独写一个静态类上,存储虚拟机的类适配到原生interface或者虚拟机的函数适配到原生delegate,该类不能放Editor目录
[Configure]注入配置类只能放在单独写一个存放在Editor目录下的类上
[IFix]注入可能需要修复函数的类的集合只能放在[Configure]类的一个静态属性上
[Filter]注入不想发生注入的函数只能放在[Configure]类的一个静态函数上

[IFix.Patch]

修复某个函数,该标签只能用在方法上,直接在方法上面标注一下[IFix.Patch]即可

[IFix.Patch]
private int Add(int a,int b)
{
     return a * b;
}

[IFix.Interpret]

新增个函数或者类,在属性,方法,类型上,直接在要新增的代码上面标注一下这个标签即可。
[IFix.Patch]和[IFix.Interpret]属于比较常用的修复Bug

[IFix.CustomBridge]

在注入阶段使用; 把一个虚拟机的类适配到原生interface或者把一个虚拟机的函数适配到原生delegate。

  1. 修复代码赋值一个闭包到一个delegate变量;
  2. 修复代码的Unity协程用了yield return;
  3. 新增一个函数,赋值到一个delegate变量;
  4. 新增一个类,赋值到一个原生interface变量;
  5. 新增函数,用了yield return;
    该标签只能用在类上,写上一个静态类,里面有一个静态字段,值就是interface和delegate的类型集合,该配置类不能放到Editor目录,且不能内嵌到另外一个类里头。
[IFix.CustomBridge]
public static class AdditionalBridge
{
 static List<Type> bridge = new List<Type>()
    {
      typeof(ISubSystem),
      typeof(IEnumerator), //如果之前使用过协程,可不做处理
      typeof(Test.MyDelegate)
    };
}

[IFix.Filter]

在注入阶段使用,过滤某些方法。在注入阶段,凡是在[IFix]标签下的属性里面的值,都会被注入适配代码,但如果不想对某个函数进行注入,可以用该标签进行过滤。该标签只能用在方法上,Configure类中的一个静态方法。

[Filter]
static bool Filter(System.Reflection.MethodInfo methodInfo)
{
    return methodInfo.DeclaringType.FullName == "Test" 
    && (methodInfo.Name == "Test2" || methodInfo.Name == "Test1");
}

注意事项

如果觉得某个类的函数可能会需要修复,那么一定要把该类放到Editor目录下[Configure]类的[IFix]静态字段里;然后才可以对某个函数进行[IFix.Patch]。
涉及到interface和delegate,如果把一个虚拟机的类适配到原生interface或者把一个虚拟机的函数适配到原生delegate ,一定要放到[IFix.CustomBridge]类的静态字段里。
打上[Configure]标签的类,必须放在Editor目录下。
[IFix],[Filter]这些标签必须放在打上[Configure]标签的类里。
在[IFix.Patch]时,不支持修复泛型函数,不支持修复构造函数,不支持在原生类中新增字段。
在[IFix.Interpret]时,不支持新增类继承原生类,不支持新增类是泛型类。


项目中使用

【注入(发包前)】

  1. 可以单独对一个类型或者一个dll进行插桩配置,打包的时候会自动注入到程序集中去。
  2. 要修复Assets下dll的类,需要在InjectFixAssemblys中配置对应dll的路径信息

使用PatchManager.Load(stream)加载补丁:

string[] fixFileNames = Directory.GetFiles(fullFixFilePath, "*.bytes");
foreach (var fileName in fixFileNames)
{
	FileStream stream = new FileStream(fileName, FileMode.Open);
	if (stream != null)
	{
		try
		{
			PatchManager.Load(stream);
		}
		catch (Exception e)
		{
			Debug.LogError(e.ToString());
		}

		Debug.LogFormat("InjectFix load fix file {0} success and start pathch", fileName);
		stream.Dispose();
	}
}

【Fix补丁包(发包后需要修复)】
在需要修复的代码上面加上Patch标签,生成对应平台补丁包,走热更流程,完成修复。
PS:一个程序集生成一个补丁包,多个补丁包会自动卸载上一个,即使多次Load,只有最后一个会生效。

【接入】
具体接入参考官方文档


接入遇到的问题记录

1. 注入失败,提示下面报错

System.Exception: assembly may be not injected yet, cat find IFix.ILFixInterfaceBridge, Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null

解决:

  • 确保出包之前注入成功,官方监听场景打包之后进行自动注入 ([UnityEditor.Callbacks.PostProcessScene]),确保注入之后不会触发编译,若触发编译(比如改变dll或者路径),则注入失效,需要手动调用IFixEditor.InjectAllAssemblys重新注入
  • 如果需要注入自定义程序集,需要改官方源码,官方会限定注入project\Library\ScriptAssemblies(PlayerScriptAssemblies) 路径下的程序集,若要注入Assets/Plugins下的程序集,需要去改对应路径和ScriptAssemblies限制,另外注入第三方dll会触发编译,需要重新注入ScriptAssemblies下的Assembly-CSharp.dll
  • 打包可能会产生临时程序集路径PlayerScriptAssemblies,注意不要注入错误路径
private static string GetScriptAssembliesFolder()
{
	var assembliesFolder = "PlayerScriptAssemblies";
	if (!Directory.Exists(string.Format("./Library/{0}/", assembliesFolder)))
	{
		assembliesFolder = "ScriptAssemblies";
	}
	return assembliesFolder;
}

2. 泛型Patch报错

Unhandled Exception:System.Exception: Utils/d__0 is CompilerGenerated

Unhandled Exception:System.InvalidProgramException: try to use a generic type definition: !0

InjectFix对于泛型支持有限制,不支持一个不确定的泛型方法,支持确定的泛型方法

3.调用PatchManager.Load时,报错can not load type

  1. 查询是否对该类型进行Inject注入
  2. 考虑是IL2CPP代码裁剪的原因

4.System.Exception: can not load field [xxx] of XXX
patch补丁有新增属性/方法/函数,并且没有用[Interpret]标注

5. System.Exception: can not load type [System.Collections.Generic.List`1+Enumerator[[xxx
在这里插入图片描述
问题: Unity打包使用.net4,会剥离netstandard.dll,因此所有的类型都是mscorlib里的类型,但是生成patch的时候代码中有依赖netstandard.dll,拿到类的引用和线上版本不一致,因此报了此错误。
解决: 生成Patch的时候调用assemblyType.Resolve(),拿到的类型和运行时保持一致
在这里插入图片描述

更多错误汇集

官方Issues

后期有坑持续记录


(完)

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cchoop

有用的话请杯肥宅水

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值