我创建了简单的程序,它动态地生成GenericEmitExample1.dll程序集。这类程序集定义了以下类型:
public class Sample
{
public static string test()
{
int num = default(int);
return num.ToString();
}
}以下是此类程序的源代码:
using System;
using System.Reflection;
using System.Reflection.Emit;
public class Example
{
public static void Main()
{
AppDomain myDomain = AppDomain.CurrentDomain;
AssemblyName myAsmName = new AssemblyName("GenericEmitExample1");
AssemblyBuilder myAssembly = myDomain.DefineDynamicAssembly(myAsmName, AssemblyBuilderAccess.RunAndSave);
ModuleBuilder myModule =
myAssembly.DefineDynamicModule(myAsmName.Name,
myAsmName.Name + ".dll");
TypeBuilder myType = myModule.DefineType("Sample", TypeAttributes.Public);
var test_method = myType.DefineMethod("test", MethodAttributes.Public | MethodAttributes.Static, typeof(String), Type.EmptyTypes);
var gen = test_method.GetILGenerator();
var local = gen.DeclareLocal(typeof(int));
gen.Emit(OpCodes.Ldloca, local);
gen.Emit(OpCodes.Constrained, typeof(int));
gen.Emit(OpCodes.Callvirt, typeof(int).GetMethod(nameof(int.ToString), Type.EmptyTypes));
gen.Emit(OpCodes.Ret);
myType.CreateType();
myAssembly.Save(myAsmName.Name + ".dll");
}
}有内置工具,名为PEVerify (https://learn.microsoft.com/en-us/dotnet/framework/tools/peverify-exe-peverify-tool)。它有助于确定它们的MSIL代码和相关元数据是否满足类型安全要求。我决定测试它,在它对生成的程序集进行调用之后,它显示了以下错误消息:
IL: Error: GenericEmitExample1.dll : Sample::test Callvirt on value type方法。 1错误验证GenericEmitExample1.dll
这样的报告令我吃惊。下面是生成类型的IL代码:
.class public auto ansi Sample
extends [mscorlib]System.Object
{
// Methods
.method public static
string test () cil managed
{
// Method begins at RVA 0x2050
// Code size 14 (0xe)
.maxstack 1
.locals init (
[0] int32
)
IL_0000: ldloca.s 0
IL_0002: constrained. [mscorlib]System.Int32
IL_0008: callvirt instance string [mscorlib]System.Int32::ToString()
IL_000d: ret
} // end of method Sample::test
.method public specialname rtspecialname
instance void .ctor () cil managed
{
// Method begins at RVA 0x206c
// Code size 7 (0x7)
.maxstack 2
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
} // end of method Sample::.ctor
} // end of class Sample我没有看到任何禁止的诡计/无效的IL码。callvirt与constrained前缀一起使用。文档(https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.opcodes.constrained?view=netframework-4.8)验证了这一技巧。这是III.2.1 constrained. – (prefix) invoke a member on a value of a variable type的报价
受约束的操作码允许IL编译器以统一的方式调用虚拟函数,而不依赖于ptr是值类型还是引用类型。虽然thisType是泛型类型变量,但约束前缀也适用于非通用类型,并且可以减少在语言中生成虚拟调用的复杂性,这些语言隐藏了值类型和引用类型之间的区别。
那么,PEVerify有什么问题呢?是虫子吗?
发布于 2019-11-18 12:29:44
来自ECMA-335的第三.2.1节(讨论constrained前缀)
Verifiability: ptr参数将是指向
thisType的托管指针(&)。此外,如上所述,callvirt指令的所有正常验证规则都适用于ptr转换之后。这相当于要求装箱thisType必须是method所属类的子类。
我认为这与“这相当于要求一个装箱的thisType必须是method所属类的子类”是不对的。
在您的例子中,method是Int32::ToString(),而不是Object::ToString(),因此属于int。但是,装箱int不是int的子类。
为了在这里使用受约束的虚拟调用,您必须调用Object::ToString(),而不是Int32::ToString()。
我通过将callvirt指令更改为:
gen.Emit(OpCodes.Callvirt, typeof(object).GetMethod(nameof(object.ToString), Type.EmptyTypes));这证明了。
此外:
I.12.1.6.2.4调用方法 值类型上的静态方法与普通类上的静态方法没有什么不同:使用带有元数据令牌的调用指令,将值类型指定为方法的类。值类型支持非静态方法(即实例方法和虚拟方法),但对它们给予特殊处理。引用类型(而不是值类型)上的非静态方法需要一个这个指针,它是该类的一个实例。这对于引用类型来说是有意义的,因为它们具有标识,而--此指针表示该标识。但是,值类型只有在装箱时才具有标识。为了解决这个问题,值类型的非静态方法上的 this 指针是值类型的byref参数,而不是普通的按值参数。 可以通过以下方式调用值类型上的非静态方法:
call指令可用于调用函数,将实例的地址作为第一个参数( this指针)传递。与调用指令一起使用的元数据令牌应该将值类型本身指定为方法的类。callvirt指令,并酌情在System.Object、System.ValueType或System.Enum类上指定方法。callvirt指令并在接口类型上指定方法。
您直接在值类型上调用一个方法(而不是在它的一个框上),所以您应该使用call。
https://stackoverflow.com/questions/58914430
复制相似问题