与如何使用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参数传递给方法。在控制流量点,可以将受控的可控指针与相同类型的托管指针合并,以产生可控的可控托管指针。可控的可控制性托管指针只能以以下方式使用:
- 作为
ldfld
,ldflda
,stfld
,call
,callvirt
或constrained. callvirt
指令的对象参数。- 作为
ldind.*
或ldobj
指令的指针参数。- 作为
cpobj
指令的源参数。所有其他操作(包括
stobj
,stind.*
,initobj
和mkrefany
(都是无效的。[...]
,但看起来仍然是正确的:
III.4.29 StoBJ - 在地址
存储一个值[...]
正确性:
正确的cil确保 dest 是
T
的指针, src 的类型是 em> verifier-aperigier-aperignable-toto to to toT
。[...]
请注意,这里没有对可控的可控性托管指针的限制,允许使用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
,所以我正在寻找。