c# - 通过 IL 替换类字段的值

标签 c# reflection cil

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

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 的私有(private)字段不会更改:

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

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

最佳答案

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

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

我最好的建议是回顾一下 OpCodes 如何MSDN 上已经列出了。这对我来说很重要,因为毕竟我不会读或说IL。

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

当您最终找到听起来与您需要的内容有点相似的页面时,请看看它自己的页面是如何布局的。

stfld例如。最重要的部分在备注部分,称为“堆栈转换行为”。本节告诉我们需要什么才能使操作码按照包装盒上的说明执行操作。

它说:

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

关于c# - 通过 IL 替换类字段的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68538500/

相关文章:

c# - 如何获取实现 IMyInterface 的引用中的所有类型

c# - 使用反射模仿 C# 数组初始化器行为

c# - 动态类型影子基类的属性并使用 Reflection.Emit 设置为 protected

c# - 更改文件地址

c# - 无法重复显示对话框

c# - 将 'Handles' 从 VB.NET 迁移到 C#

c# 正则表达式到 javascript

c# - 在 .Net 中使用私有(private)集初始化属性

Java:按名称实例化具体实现

c# - 如何验证 C# 代码?