如何在C#/ IL中改变盒装值类型(基元或结构)

时间:2017-06-23 14:35:46

标签: c# .net cil reflection.emit

How to mutate a boxed struct using IL相关我试图以通用方式更改盒装值类型的值,因此尝试实现以下方法:

void MutateValueType<T>(object o, T v) where T : struct

所以以下内容应该是可能的:

var oi = (object)17;
MutateValueType<int>(oi, 43);
Console.WriteLine(oi); // 43

var od = (object)17.7d;
MutateValueType<double>(od, 42.3);
Console.WriteLine(od); // 42.3

我无法在.NET Framework上使用它(请参阅@hvd的评论,没有typeof(Program).Module的实现适用于其他运行时)。我已经实现了这个,如下所示。但是,当使用:

调用委托del时,这会失败
System.Security.VerificationException: 'Operation could destabilize the runtime.'

以下是我提出的实施方案:

public static void MutateValueType<T>(object o, T v)
{
    var dynMtd = new DynamicMethod("EvilMutateValueType", 
        typeof(void), new Type[] { typeof(object), typeof(T) });
    var il = dynMtd.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);           // object
    il.Emit(OpCodes.Unbox, typeof(T));  // T&
    il.Emit(OpCodes.Ldarg_1);           // T (argument value)
    il.Emit(OpCodes.Stobj, typeof(T));  // stobj !!T
    il.Emit(OpCodes.Ret);               

    var del = (Action<object, T>)dynMtd.CreateDelegate(typeof(Action<object, T>));
    del(o, v);
}

上面的内容应该等同于下面的IL,这有效,但上述情况仍然失败,所以问题是为什么这不起作用。

  .method public hidebysig static void Mutate<T>(object o, !!T Value) cil managed aggressiveinlining
  {
    .maxstack  2
    ldarg.0
    unbox !!T
    ldarg.1
    stobj !!T
    ret
  }

2 个答案:

答案 0 :(得分:4)

不同之处在于默认情况下DynamicMethod需要可验证的代码,而默认情况下您自己的代码(包括自定义IL)是无法验证的。

您可以将DynamicMethod视为您自己模块的一部分,通过指定模块允许它包含无法验证的IL:

var dynMtd = new DynamicMethod("EvilMutateValueType",
    typeof(void), new Type[] { typeof(object), typeof(T) }, typeof(Program).Module);
// Use whatever class you have available here.              ^^^^^^^^^^^^^^^^^^^^^^

尽管PEVerify中的其他一些问题使得很难获得良好的诊断,但看起来这至少是不可验证的:

  

III.1.8.1.2.2受控可变性管理指针

     

readonly.前缀和unbox指令可以生成所谓的受控可变性管理指针。与普通的托管指针类型不同,受控可变性托管指针不是 verifier-assignable-to (§III.1.8.1.2.3)普通托管指针;例如,它不可能   作为byref参数传递给方法。在控制流点,受控可变性管理指针可以与相同类型的托管指针合并,以产生受控可变性管理指针。

     

受控可变性管理指针只能以下列方式使用:

     
      
  1. 作为ldfldldfldastfldcallcallvirtconstrained. callvirt指令的对象参​​数。
  2.   
  3. 作为ldind.*ldobj指令的指针参数。
  4.   
  5. 作为cpobj指令的源参数。
  6.         

    所有其他操作(包括stobjstind.*initobjmkrefany)均无效。

         

    [...]

但看起来它仍然是正确的:

  

III.4.29 stobj - 将值存储在地址

     

[...]

     

<强> 正确性:

     

正确的CIL确保 dest 是指向T的指针,而 src 的类型是 verifier-assignable-to { {1}}。

     

[...]

请注意,此处对受控可变性管理指针没有限制,任何指向T的指针都是允许的。

因此,确保您的IL无法进行验证是正确的方法。

答案 1 :(得分:2)

一种解决方案是在IL中使用Unbox方法调用unbox并将ref返回到对象中包含的类型:

.method public hidebysig static !!T&  Unbox<T>(object o) cil managed aggressiveinlining
{
  .maxstack  1
  ldarg.0
  unbox !!T
  ret
}

然后使用它:

public static void MutateValueType<T>(object o, T v)
{
    ref T ub = ref Unsafe.Unbox<T>(o);
    ub = v;
}

这正确输出:

        var oi = (object)17;
        MutateValueType<int>(oi, 43);
        Console.WriteLine(oi); // 43

        var od = (object)17.7d;
        MutateValueType<double>(od, 42.3);
        Console.WriteLine(od); // 42.3

注意:这需要C#7支持ref返回。

这可能会添加到https://github.com/DotNetCross/Memory.Unsafe,但也必须使用il.Emit,所以我正在寻找。