微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

通过 IL 替换类字段的值

如何解决通过 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 类中按字母顺序排列在一个大列表中。这绝对是无用的,因为诸如哪些代码推送堆栈或哪些代码弹出没有放在一起等关键特性,并且执行多项操作(如推送和弹出多个对象)的 OpCodes 也没有分组。这让我们别无选择,只能阅读每个操作码描述并记住它。好玩!

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

stfld为例。最重要的部分是在称为“堆栈转换行为”的备注部分。本节告诉我们需要什么才能让 OpCode 执行它在盒子上所说的。

它说:

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

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

然后当我调用 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));
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。