Skip to content

Extension Points

Ling edited this page Jul 22, 2026 · 4 revisions

注入点与修改操作

PCL.Mixin 没有预先枚举的“工具页扩展点”或“设置页扩展点”。扩展点由目标方法和 IL 指令共同定义。

MixinAt

注入点 定位对象 常见用途
Head 方法入口 校验参数、短路执行、初始化
Return 每个返回位置 查看或修改返回值
Tail 方法最终尾部返回 收尾逻辑
Invoke 方法调用指令 调用前后注入或 Redirect
InvokeAssign 调用结果赋值后 观察已保存的调用结果
Field 字段读取或写入 观察或重定向字段访问
New 对象构造 观察构造或替换相关行为
Constant 常量加载 定位或修改固定值
Jump 分支跳转 在条件分支附近注入
Load 局部变量读取 观察或修改局部变量值
Store 局部变量写入 观察或修改写入值

C# 枚举名称使用 PascalCase,例如 MixinAt.InvokeAssign;日志中显示为大写注入点。

HEAD、RETURN 与 TAIL

[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)

使用完整类型名。重载方法应写参数类型,否则可能产生歧义并拒绝应用。

INVOKE

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 当作唯一定位条件。

INVOKE_ASSIGN

[Inject("Run", At = MixinAt.InvokeAssign,
    Target = "Example.Target::Echo(System.Int32)")]
private static void AfterAssigned()
{
}

它用于调用已经产生结果并完成赋值后的边界。若调用结果没有形成可识别的赋值位置,匹配可能失败。

FIELD

[Inject("Read", At = MixinAt.Field,
    Target = "Example.Target::Value", Ordinal = 0)]
private static void BeforeFieldAccess()
{
}

需要替换字段读取或写入行为时使用 Redirect(At = MixinAt.Field),并保证处理器签名与字段指令栈一致。

NEW

[Inject("Create", At = MixinAt.New,
    Target = "System.Text.StringBuilder::.ctor()")]
private static void OnNew()
{
}

构造函数描述符应包含 .ctor 和参数类型。

CONSTANT

[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 缩小范围。

JUMP

[Inject("Branch", At = MixinAt.Jump, Ordinal = 0)]
private static void BeforeFirstJump()
{
}

JUMP 对目标方法编译后的控制流敏感。发布前应在实际 Release 构建的启动器上验证。

LOAD 与 STORE

观察局部变量指令:

[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 和匹配约束。

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 边界可以使用 HEADTAIL 或可解析的指令目标。范围解析失败会根据 required/optional 规则处理。

Shift

[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 可能越过栈状态变化或异常处理边界。能用 BeforeAfter 或更精确 Target 时,不要使用较大的偏移。

选择最小修改

按兼容性从相对稳定到高风险,通常优先考虑:

  1. HEAD / RETURN 边界 Inject。
  2. 带完整描述符的 INVOKE Inject、ModifyArgRedirect
  3. FIELDCONSTANTLOADSTORE 和 Slice。
  4. JUMP、较大的 AtShift.By
  5. Overwrite

所有指令级修改都应使用 RequireExpectAllow 和真实启动器构建测试来发现目标漂移。

Clone this wiki locally