如何在 C#/IL 中改变装箱值类型(原始或结构)
How to mutate a boxed value type (primitive or struct) in C#/IL
与 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
}
一个解决方案是在 IL 中创建一个 Unbox
方法,该方法调用 unbox
和 returns 一个 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
returns。
这可能是为了说 https://github.com/DotNetCross/Memory.Unsafe 但它也必须与 il.Emit
一起使用,所以我正在寻找它。
不同之处在于,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 Controlled-mutability managed pointers
The readonly.
prefix and unbox
instructions can produce what is called a controlled-mutability managed pointer. Unlike ordinary managed pointer types, a controlled-mutability managed pointer is not verifier-assignable-to (§III.1.8.1.2.3) ordinary managed pointers; e.g., it cannot be
passed as a byref argument to a method. At control flow points, a controlled-mutability managed pointer can be merged with a managed pointer of the same type to yield a controlled-mutability managed pointer.
Controlled-mutability managed pointers can only be used in the following ways:
- As the object parameter for an
ldfld
, ldflda
, stfld
, call
, callvirt
, or constrained. callvirt
instruction.
- As the pointer parameter to a
ldind.*
or ldobj
instruction.
- As the source parameter to a
cpobj
instruction.
All other operations (including stobj
, stind.*
, initobj
, and mkrefany
) are invalid.
[...]
但看起来它仍然是正确的:
III.4.29 stobj – store a value at an address
[...]
Correctness:
Correct CIL ensures that dest is a pointer to T
and the type of src is verifier-assignable-to T
.
[...]
请注意,此处对受控可变性托管指针没有限制,任何指向 T
的指针都是允许的。
因此,确保不对您的 IL 进行验证是正确的方法。
与 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
}
一个解决方案是在 IL 中创建一个 Unbox
方法,该方法调用 unbox
和 returns 一个 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
returns。
这可能是为了说 https://github.com/DotNetCross/Memory.Unsafe 但它也必须与 il.Emit
一起使用,所以我正在寻找它。
不同之处在于,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 Controlled-mutability managed pointers
The
readonly.
prefix andunbox
instructions can produce what is called a controlled-mutability managed pointer. Unlike ordinary managed pointer types, a controlled-mutability managed pointer is not verifier-assignable-to (§III.1.8.1.2.3) ordinary managed pointers; e.g., it cannot be passed as a byref argument to a method. At control flow points, a controlled-mutability managed pointer can be merged with a managed pointer of the same type to yield a controlled-mutability managed pointer.Controlled-mutability managed pointers can only be used in the following ways:
- As the object parameter for an
ldfld
,ldflda
,stfld
,call
,callvirt
, orconstrained. callvirt
instruction.- As the pointer parameter to a
ldind.*
orldobj
instruction.- As the source parameter to a
cpobj
instruction.All other operations (including
stobj
,stind.*
,initobj
, andmkrefany
) are invalid.[...]
但看起来它仍然是正确的:
III.4.29 stobj – store a value at an address
[...]
Correctness:
Correct CIL ensures that dest is a pointer to
T
and the type of src is verifier-assignable-toT
.[...]
请注意,此处对受控可变性托管指针没有限制,任何指向 T
的指针都是允许的。
因此,确保不对您的 IL 进行验证是正确的方法。