How to mutate a boxed struct using IL

人盡茶涼 提交于 2019-11-28 20:05:31

问题


Imagine we have a mutable struct (yes, don't start):

public struct MutableStruct
{
    public int Foo { get; set; }
    public override string ToString()
    {
        return Foo.ToString();
    }
}

Using reflection, we can take a boxed instance of this struct and mutate it inside the box:

// this is basically what we want to emulate
object obj = new MutableStruct { Foo = 123 };
obj.GetType().GetProperty("Foo").SetValue(obj, 456);
System.Console.WriteLine(obj); // "456"

What I would like to do is to write some IL that can do the same as this - but faster. I'm a meta-programming junkie ;p

It is trivial to unbox-any the value and mutate the value using regular IL - but you can't just call box it afterwards because that will create a different box. I'm guessing that what we would need to do here is copy it over the existing box. I have investigated ldobj / stobj, but those don't seem to do the job (unless I'm missing something).

So: does a mechanism to do this exist? Or must I limit myself to reflection to perform in-place updates of boxed structs ?

Or in other words: what ... evil goes here... ?

var method = new DynamicMethod("evil", null,
    new[] { typeof(object), typeof(object) });
var il = method.GetILGenerator();
// ... evil goes here...
il.Emit(OpCodes.Ret);

Action<object, object> action = (Action<object, object>)
    method.CreateDelegate(typeof(Action<object, object>));
action(obj, 789);
System.Console.WriteLine(obj); // "789"

回答1:


Well, that was fun.

Using Ldflda and Stind_* seems to work. Actually, it's mostly Unbox (see history for version that works with Ldflda and Stind_*).

Here's what I hacked together in LinqPad to prove it out.

public struct MutableStruct
{
    public int Foo { get; set; }

    public override string ToString()
    {
        return Foo.ToString();
    }
}

void Main()
{
    var foo = typeof(MutableStruct).GetProperty("Foo");
    var setFoo = foo.SetMethod;

    var dynMtd = new DynamicMethod("Evil", typeof(void), new [] { typeof(object), typeof(int) });
    var il = dynMtd.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);                       // object
    il.Emit(OpCodes.Unbox, typeof(MutableStruct));  // MutableStruct&
    il.Emit(OpCodes.Ldarg_1);                       // MutableStruct& int
    il.Emit(OpCodes.Call, setFoo);                  // --empty--
    il.Emit(OpCodes.Ret);                           // --empty--

    var del = (Action<object, int>)dynMtd.CreateDelegate(typeof(Action<object, int>));

    var mut = new MutableStruct { Foo = 123 };

    var boxed= (object)mut;

    del(boxed, 456);

    var unboxed = (MutableStruct)boxed;
    // unboxed.Foo = 456, mut.Foo = 123
}



回答2:


Here you go:

Just use unsafe :)

static void Main(string[] args)
{
  object foo = new MutableStruct {Foo = 123};
  Console.WriteLine(foo);
  Bar(foo);
  Console.WriteLine(foo);
}

static unsafe void Bar(object foo)
{
  GCHandle h = GCHandle.Alloc(foo, GCHandleType.Pinned);

  MutableStruct* fp = (MutableStruct*)(void*)  h.AddrOfPinnedObject();

  fp->Foo = 789;
}

IL implementation is left as an exercise to the reader.

Update:

Based on the Kevin's answer, here is a minimal working example:

ldarg.0
unbox      MutableStruct
ldarg.1
call       instance void MutableStruct::set_Foo(int32)
ret



回答3:


You can do this even easier. Try this under .NET 4.5 where we have dynamic.

struct Test
{
    public Int32 Number { get; set; }


    public override string ToString()
    {
        return this.Number.ToString();
    }
}


class Program
{
    static void Main( string[] args )
    {
        Object test = new Test();

        dynamic proxy = test;

        proxy.Number = 1;

        Console.WriteLine( test );
        Console.ReadLine();
    }
}

I know it's not reflection but still fun though.




回答4:


Even without unsafe code, pure C#:

using System;

internal interface I {
  void Increment();
}

struct S : I {
  public readonly int Value;
  public S(int value) { Value = value; }

  public void Increment() {
    this = new S(Value + 1); // pure evil :O
  }

  public override string ToString() {
    return Value.ToString();
  }
}

class Program {
  static void Main() {
    object s = new S(123);
    ((I) s).Increment();
    Console.WriteLine(s); // prints 124
  }
}

In C#, this reference inside value types instance methods actually is ref-parameter (or out-parameter in value type constructor, and that is why this can't be captured into closures, just like ref/out parameters in any methods) and can be modified.

When struct instance method is invoked on unboxed value, this assignment will effectively replace value at the call site. When instance method is invoked on boxed instance (via virtual call or interface call like in the example above), ref-parameter is pointed to the value inside the box object, so it is possible to modify boxed value.




回答5:


I've posted a solution using Expression Trees for setting fields in another thread. It's trivial to change the code to use properties instead:



来源:https://stackoverflow.com/questions/18937935/how-to-mutate-a-boxed-struct-using-il

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!