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");
}

项目中使用

【注入(发包前)】

  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重新注入
  • 如果需要注入自定义程序集,需要改官方源码,官方会限定注入projectLibraryScriptAssemblies 路径下的程序集,若要注入Assets/Plugins下的程序集,需要去改对应路径和ScriptAssemblies限制,另外注入第三方dll会触发编译,需要重新注入ScriptAssemblies下的Assembly-CSharp.dll

2. 泛型Patch报错

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

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

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

更多错误汇集

官方Issues

后期有坑持续记录


赞赏与支持

【收藏与点赞】
觉得写的不错的可以收藏点赞转发噢~

【请作者喝杯咖啡】
觉得写的不错的可以请作者喝杯咖啡噢~
您的支持是我创作和分享的动力!
在这里插入图片描述


(完)

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>