通过 IL 替换 class 字段的值

Replacing a class field's value via IL

在我努力学习和理解 IL 的过程中,我试图替换对象中私有字段的值,但是它不起作用。

public class Example
{
    private int _value;
}

private delegate void _memberUpdaterByRef(ref object source, object value);
private _memberUpdaterByRef GetWriterForField(FieldInfo field)
{
    // dynamically generate a new method that will emit IL to set a field value
    var type = field.DeclaringType;
    var dynamicMethod = new DynamicMethod(
        $"Set{field.Name}",
        typeof(void),
        new Type[] { typeof(object).MakeByRefType(), typeof(object) },
        type.Module,
        true
    );

    var gen = dynamicMethod.GetILGenerator();

    var typedSource = gen.DeclareLocal(field.DeclaringType);
    gen.Emit(OpCodes.Ldarg_0); // Load the instance of the object (argument 0) onto the stack
    gen.Emit(OpCodes.Ldind_Ref); // load as a reference type
    gen.Emit(OpCodes.Unbox_Any, field.DeclaringType);
    gen.Emit(OpCodes.Stloc_0); // pop typed arg0 into temp

    gen.Emit(OpCodes.Ldloca_S, typedSource);
    gen.Emit(OpCodes.Ldarg_1); // Load the instance of the object (argument 1) onto the stack
    gen.Emit(OpCodes.Unbox_Any, field.FieldType);
    gen.Emit(OpCodes.Stfld, field);

    gen.Emit(OpCodes.Ldarg_0); // Load the instance of the object (argument 0) onto the stack
    gen.Emit(OpCodes.Ldloc_0); // push temp
    gen.Emit(OpCodes.Box, field.DeclaringType);
    gen.Emit(OpCodes.Stind_Ref); // store object reference
    gen.Emit(OpCodes.Ret); // return void

    // create a delegate matching the parameter types
    return (_memberUpdaterByRef)dynamicMethod.CreateDelegate(typeof(_memberUpdaterByRef));
}

给定以下伪代码,名为 _value 的私有字段不会更改:

// field = Example._value
var writer = GetWriterForField(field);
writer(ref newInstance, 100); // Example._value = 0

我不太确定如何调试它,或者我的 IL 语法有什么不正确的地方。我几乎在学习 IL 并从不同的来源获取信息,试图让它发挥作用。

使用您的原始 IL,我发现总的来说您做对了。但是,在您的实现中,当您将源对象分配给本地对象并尝试设置其字段时,由于某种原因从未设置原始对象中的字段。

通过一些试验和错误,我发现您可以通过简单地使用地址(ref object 参数)而不是将其存储在本地只是为了稍后将其推送到堆栈来避免一起使用局部变量.

我最好的建议是复习一下 MSDN 上 OpCodes 的布局。这对我来说很重要,因为毕竟我不会阅读或说 IL。

我发现 OpCode MSDN 按字母顺序排列在 OpCode class 中的一个大列表中。这绝对是无用的,因为关键功能,例如哪些代码压入堆栈或哪些代码弹出没有放在一起,并且执行多个操作(例如压入和弹出多个对象)的 OpCodes 也没有分组。这让我们别无选择,只能阅读每一个 OpCodes 描述并记住它。好玩!

当您最终找到听起来与您需要的内容有点相似的内容时,请查看其自身页面的布局方式。

stfld为例。最重要的部分位于名为 'The Stack Transitional behaviour' 的备注部分。这部分告诉我们需要什么才能让 OpCode 执行它在盒子上的说明。

它说:

  • An object reference or pointer is pushed onto the stack.
  • A value is pushed onto the stack.
  • The value and the object reference/pointer are popped from the stack; the value of field in the object is replaced with the supplied value.

我阅读这篇文章的方式 - 为了让我理解它,
“我是那个需要将对象推入堆栈的人,然后 我是需要将值压入堆栈的人。"

然后当我调用 generator.Emit(OpCode.stfld,field) 时,最后一行告诉我之后 期望 在堆栈上的内容。这没什么,因为它弹出两个值并且不替换任何内容。这正是我们想要的!

有了阅读他们的知识,尽管布局很糟糕(除了在他们的各个页面上缺少每个 OpCode 的示例之外),我们可以找到一种方法来完成使用原始 IL 的工作。

我继续为您准备了一个工作示例,希望评论能对其进行分解。

public _memberUpdaterByRef GetWriterForField(FieldInfo field)
{
    Type[] args = { typeof(object).MakeByRefType(), typeof(object) };
    var method = new DynamicMethod(
        $"Set{field.Name}",
        typeof(void),
        args,
        field.DeclaringType.Module
    );

    var gen = method.GetILGenerator();

    // because arg 0 is a ref, ldarg pushes arg0's address to the stack instead of the object/value
    gen.Emit(OpCodes.Ldarg_0);

    // in order to set the value of a field on on the object we cant use it's address
    // pop the address and push the instance to the stack
    gen.Emit(OpCodes.Ldind_Ref);

    // push the value that the field should be set to, to the stack
    gen.Emit(OpCodes.Ldarg_1);

    // becuase the value may be either a reference or value type, unbox it
    gen.Emit(OpCodes.Unbox_Any, field.FieldType);

    // pop the instance of the object, pop the value, and set the value of the field
    gen.Emit(OpCodes.Stfld, field);

    // no remaining objects on the stack, ret to exit
    gen.Emit(OpCodes.Ret);

    // create and return the delegate
    return (_memberUpdaterByRef)method.CreateDelegate(typeof(_memberUpdaterByRef));
}