-
Notifications
You must be signed in to change notification settings - Fork 1
Extension Points
PCL.Mixin 没有预先枚举的“工具页扩展点”或“设置页扩展点”。扩展点由目标方法和 IL 指令共同定义。
| 注入点 | 定位对象 | 常见用途 |
|---|---|---|
Head |
方法入口 | 校验参数、短路执行、初始化 |
Return |
每个返回位置 | 查看或修改返回值 |
Tail |
方法最终尾部返回 | 收尾逻辑 |
Invoke |
方法调用指令 | 调用前后注入或 Redirect |
InvokeAssign |
调用结果赋值后 | 观察已保存的调用结果 |
Field |
字段读取或写入 | 观察或重定向字段访问 |
New |
对象构造 | 观察构造或替换相关行为 |
Constant |
常量加载 | 定位或修改固定值 |
Jump |
分支跳转 | 在条件分支附近注入 |
Load |
局部变量读取 | 观察或修改局部变量值 |
Store |
局部变量写入 | 观察或修改写入值 |
C# 枚举名称使用 PascalCase,例如 MixinAt.InvokeAssign;日志中显示为大写注入点。
[Inject("Run", At = MixinAt.Head)]
private static void BeforeRun()
{
}
[Inject("Compute", At = MixinAt.Return, Cancellable = true)]
private static void BeforeEachReturn(CallbackInfo<int> callback)
{
callback.ReturnValue += 1;
}
[Inject("Compute", At = MixinAt.Tail, Cancellable = true)]
private static void AtTail(CallbackInfo<int> callback)
{
callback.ReturnValue += 5;
}需要取消原方法时必须声明 Cancellable。返回值方法使用 CallbackInfo<T>,void 方法使用 CallbackInfo。
方法调用通常写成:
Namespace.Type::Method(System.Int32,System.String)
字段通常写成:
Namespace.Type::FieldName
构造函数通常写成:
Namespace.Type::.ctor(System.Int32)
使用完整类型名。重载方法应写参数类型,否则可能产生歧义并拒绝应用。
private const string EchoTarget =
"Example.Target::Echo(System.Int32)";
[Inject("Run", At = MixinAt.Invoke, Target = EchoTarget,
Ordinal = 0, Shift = AtShift.Before)]
private static void BeforeEcho()
{
}Opcode 可以进一步限制 IL 操作码。除非你确认编译器输出,否则不应把 Opcode 当作唯一定位条件。
[Inject("Run", At = MixinAt.InvokeAssign,
Target = "Example.Target::Echo(System.Int32)")]
private static void AfterAssigned()
{
}它用于调用已经产生结果并完成赋值后的边界。若调用结果没有形成可识别的赋值位置,匹配可能失败。
[Inject("Read", At = MixinAt.Field,
Target = "Example.Target::Value", Ordinal = 0)]
private static void BeforeFieldAccess()
{
}需要替换字段读取或写入行为时使用 Redirect(At = MixinAt.Field),并保证处理器签名与字段指令栈一致。
[Inject("Create", At = MixinAt.New,
Target = "System.Text.StringBuilder::.ctor()")]
private static void OnNew()
{
}构造函数描述符应包含 .ctor 和参数类型。
[Inject("GetLimit", At = MixinAt.Constant, Target = "int:10")]
private static void BeforeConstant()
{
}
[ModifyConstant("GetLimit", Target = "int:10")]
private static int ReplaceConstant(int value) => value * 2;如果同一方法中有多个相同常量,使用 Ordinal 或 Slice 缩小范围。
[Inject("Branch", At = MixinAt.Jump, Ordinal = 0)]
private static void BeforeFirstJump()
{
}JUMP 对目标方法编译后的控制流敏感。发布前应在实际 Release 构建的启动器上验证。
观察局部变量指令:
[Inject("Run", At = MixinAt.Load, Target = "0", Ordinal = 0)]
private static void OnLoad()
{
}
[Inject("Run", At = MixinAt.Store, Target = "0", Ordinal = 0)]
private static void OnStore()
{
}修改加载或写入值:
[ModifyVariable("Run", At = MixinAt.Load, Index = 0, Ordinal = 0)]
private static int ModifyLoad(int value) => value + 10;局部变量槽位会随编译器、优化和目标代码修改而改变。尽量配合目标方法签名、Slice 和匹配约束。
直接范围:
[Inject("Run", At = MixinAt.Invoke, Target = EchoTarget,
SliceFrom = "int:2", SliceTo = "int:3")]
private static void InRange()
{
}命名范围:
[Slice("middle", From = "int:2", To = "TAIL")]
[Inject("Run", At = MixinAt.Invoke, Target = EchoTarget,
Slice = "middle", Ordinal = 0)]
private static void InNamedRange()
{
}Slice 边界可以使用 HEAD、TAIL 或可解析的指令目标。范围解析失败会根据 required/optional 规则处理。
[Inject("Run", At = MixinAt.Invoke, Target = EchoTarget,
Shift = AtShift.After)]
private static void AfterCall()
{
}
[Inject("Run", At = MixinAt.Invoke, Target = EchoTarget,
Shift = AtShift.By, By = 2)]
private static void TwoInstructionsLater()
{
}By 可能越过栈状态变化或异常处理边界。能用 Before、After 或更精确 Target 时,不要使用较大的偏移。
按兼容性从相对稳定到高风险,通常优先考虑:
-
HEAD/RETURN边界 Inject。 - 带完整描述符的
INVOKEInject、ModifyArg或Redirect。 -
FIELD、CONSTANT、LOAD、STORE和 Slice。 -
JUMP、较大的AtShift.By。 -
Overwrite。
所有指令级修改都应使用 Require、Expect、Allow 和真实启动器构建测试来发现目标漂移。