如何在 C#/IL 中改变装箱值类型(原始或结构)

nie*_*ras 2 .net c# cil reflection.emit

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

void MutateValueType<T>(object o, T v) where T : struct
Run Code Online (Sandbox Code Playgroud)

所以以下应该是可能的:

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
Run Code Online (Sandbox Code Playgroud)

我无法让它在 .NET Framework 上运行(请参阅 @hvd 的评论,该实现没有typeof(Program).Module适用于其他运行时)。我已经实现了这一点,如下所示。但是,使用以下命令调用委托时会失败del

System.Security.VerificationException: 'Operation could destabilize the runtime.'
Run Code Online (Sandbox Code Playgroud)

这是我提出的实现:

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);
}
Run Code Online (Sandbox Code Playgroud)

上面应该等同于下面的 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
  }
Run Code Online (Sandbox Code Playgroud)

小智 5

不同之处在于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.              ^^^^^^^^^^^^^^^^^^^^^^
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  1. 作为ldfld, ldflda, stfld, call, callvirt, 或constrained. callvirt指令的对象参数。
  2. 作为ldind.*orldobj指令的指针参数。
  3. 作为cpobj指令的源参数。

所有其他操作(包括stobjstind.*initobj,和mkrefany)是无效的。

[...]

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

III.4.29 stobj – 在地址中存储一个值

[...]

正确性:

正确的 CIL 确保dest是一个指针,T并且src的类型是verifier-assignable-to T

[...]

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

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