Unity注入模块,可以运行时改变被注入函数实现。
Verified Verisons:
Unity 2021.3.x | Unity 2022.3.x |
---|---|
✔️ | ✔️ |
开发此模块的最初动机是修改UnityEngine的源码。对这样一个具体问题一般化分析和设计后,最终实现顺便支持了用户自定义的装饰器和AOP。
Unity的注入模块已经有一些其他大神的实现了,为什么还要造一个轮子呢?原因如下:
- 基于UnityEditor。一些注入模块,需要依赖外部工具;而此实现在完全在UnityEditor下注入。
- 支持IL2CPP。一些注入模块只支持Editor和Mono;而此实现支持所有平台和选项。
- 支持修改引擎源码。一些注入模块只能修改用户代码,无法注入引擎代码;而此实现可以注入引擎和用户代码。
直接修改DLL文件是可行的,但是存在以下问题:
- 无法记录所做的修改
- 不方便团队共享和版本控制
- 不能改变Unity版本
- 修改步骤繁琐,容易误操作
step 1. 安装依赖库:DirectRetrieveAttribute
step 2. 通过git url安装
execute command line:
openupm add com.bbbirder.injection
一个修改Debug.Log
的例子
using com.bbbirder.injection;
using UnityEngine;
// this illustration shows how to hook `Debug.Log`
public class FirstPatch
{
// the field to be overwrited to original method
static Action<object> RawLog;
static void Log(object msg){
return RawLog.Invoke("[msg] "+msg);
}
internal class MethodReplacer : IInjection
{
// implement method: ProvideInjections
public IEnumerable<InjectionInfo> ProvideInjections()
{
yield return InjectionInfo.Create<Action<object>>(
Debug.Log, // replace Debug.Log
FirstPatch.Log, // with FirstPatch.Log
f => FirstPatch.RawLog = f // save origin method to FirstPatch.RawLog
);
}
}
}
自定类继承自IInjection
,并实现接口
ProvideInjections
中返回一个或多个InjectionInfo
初始化的时候调用:
FixHelper.InstallAll();// 查找所有注入标记,并使生效
测试成果:
Debug.Log("hello"); //output: [msg] hello
更多使用方法参考附带的Sample工程
Problem | Reason | Solution |
---|---|---|
文档示例中的异步方法无法打印完整 | WebGL平台不支持多线程 | 文档中使用的是Task,改成UniTask或其他方式即可 |
注入时未搜索到标记的方法 | Managed Stripping Level 过高,Attribute被移除 |
降低Stripping Level或 保留代码 |
注入时报UnauthorizedAccessException 或cannot access file |
文件访问权限不够 | 管理员运行 或 修改目标文件夹的安全设置(属性-安全-编辑,添加当前用户的完全控制) |
打包时报the same key has already been added. Key: mscorlib |
不支持的Unity版本,程序集依赖树包含多个不同版本的mscorlib | 暂不提供解决方案,可自行修改类型引入部分逻辑,如果这个问题影响的人多则解决之 |
UnityInjection在编译时织入,不用担心运行时兼容性
织入时机:
- 运行时:在打包的Link阶段修改DLL,如此使Runtime生效
- 编辑器时:Domain Reload
- 更多Unity版本测试,有问题提ISSUE附Unity版本,或者PR。
- Editor加速注入
当前的方法在Editor下也会执行织入,这将导致domain reload一定程度变慢,这对于超大代码库来说是难以忍受的。 事实上Editor下的织入不是必要的。作者忙完本职工作后,会来完善此处。
- 支持泛型
泛型的支持有一些特殊性,这种特殊性需要额外的工作:
Inject Phase | Fix Phase | |
---|---|---|
AOT approach | 与非泛型方法相同 | - |
RT approach | 不同的GenericInstance有时共享同一个方法体,有时又独享之。如:Full Generic Sharing方法存在一个共享的方法体;普通Generic Sharing则按照泛型类型内存大小共享方法体 | 泛型实例是运行时创建的。UnityInjection需要实现一种GenericInstantiation(classInst&methodInst)遍历的技术。这种技术对于IL2CPP backend来说,可以轻易实现;但是对于Mono backend,则需要对cctor的额外注入。 |