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



与如何使用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的实现在其他运行时间上可以使用(。我已经实施了如下所示。但是,当用A调用委托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
  }

区别在于默认情况下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.              ^^^^^^^^^^^^^^^^^^^^^^

尽管其他一些问题使得难以获得良好的诊断,但看起来至少旨在不可证实:

III.1.8.1.2.2

readonly.前缀和unbox指令可以产生所谓的a 可控性托管指针。与普通的托管指针类型不同,可控的可控托管指针不是 verifier-aperignable-to-em>(§iii.1.8.1.2.3(普通托管指针;例如,不可能 以BYREF参数传递给方法。在控制流量点,可以将受控的可控指针与相同类型的托管指针合并,以产生可控的可控托管指针。

可控的可控制性托管指针只能以以下方式使用:

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

所有其他操作(包括stobjstind.*initobjmkrefany(都是无效的。

[...]

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

III.4.29 StoBJ - 在地址

存储一个值

[...]

正确性:

正确的cil确保 dest T的指针, src 的类型是 em> verifier-aperigier-aperignable-toto to to to T

[...]

请注意,这里没有对可控的可控性托管指针的限制,允许使用T的任何指针。

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

一个解决方案是通过在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,所以我正在寻找。

相关内容

  • 没有找到相关文章

最新更新