【问题标题】:Replacing a class field's value via IL通过 IL 替换类字段的值
【发布时间】:2021-07-27 03:12:52
【问题描述】:

为了学习和理解 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 并从不同来源获取一些信息,试图让它发挥作用。

【问题讨论】:

  • 对我帮助很大的是sharplab.io 在那里您可以查看源代码如何转换为 IL。
  • @CSharpie 感谢该工具,我不知道!比使用 ildasm 好多了。我很难找到任何可以反编译和显示 C# 和 IL 的桌面工具。 dotPeek 我想是这样,但我希望找到一个可以将 C# IL 转换为 C#/IL 的工具,这非常小众。

标签: c# reflection cil


【解决方案1】:

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

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

我最好的建议是查看 MSDN 上的 OpCodes 是如何布局的。这对我来说很重要,因为毕竟我不会阅读或说 IL。

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

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

stfld 为例。最重要的部分在名为“堆栈过渡行为”的备注部分中。本节告诉我们要让 OpCode 执行盒子上所说的操作需要什么。

上面写着:

  • 对象引用或指针被压入堆栈。
  • 一个值被压入堆栈。
  • 值和对象引用/指针从堆栈中弹出;对象中的字段值将替换为提供的值。

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

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

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

我继续为您提供了一个可行的示例,希望 cmets 能够将其分解。

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

【讨论】:

  • 多么出色且解释清楚的回应。我确认这解决了这个问题,非常感谢,因为我花了将近一天的时间试图找出问题所在。我有很多东西要学习 IL,而且文档并不总是很容易理解。谢谢!
猜你喜欢
  • 2018-11-24
  • 1970-01-01
  • 1970-01-01
  • 2023-04-08
  • 2011-05-21
  • 1970-01-01
  • 2021-08-05
  • 2019-03-23
  • 2017-10-14
相关资源
最近更新 更多