与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
}
答案 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参数传递给方法。在控制流点,受控可变性管理指针可以与相同类型的托管指针合并,以产生受控可变性管理指针。受控可变性管理指针只能以下列方式使用:
- 作为
ldfld
,ldflda
,stfld
,call
,callvirt
或constrained. callvirt
指令的对象参数。- 作为
ldind.*
或ldobj
指令的指针参数。- 作为
醇>cpobj
指令的源参数。所有其他操作(包括
stobj
,stind.*
,initobj
和mkrefany
)均无效。[...]
但看起来它仍然是正确的:
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
,所以我正在寻找。