我创建了一个简单的程序,该程序可以动态生成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://docs.microsoft.com/en-us/dotnet/framework/tools/peverify-exe-peverify-tool)的内置工具。它有助于确定其MSIL代码和关联的元数据是否满足类型安全要求。我决定对其进行测试,在它调用生成的程序集后,它显示以下错误消息:
[IL]:错误:[GenericEmitExample1.dll:Sample :: test] [偏移量0x00000008]值类型方法上的Callvirt。
验证GenericEmitExample1.dll时出现1个错误
这种报道使我感到惊讶。这是生成类型的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://docs.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有什么问题?是虫子吗?
答案 0 :(得分:2)
摘自ECMA-335的第III.2.1节(讨论了constrained
前缀)
可验证性:
ptr参数将是指向
thisType
的托管指针(&)。另外,如上所述,callvirt
指令的所有常规验证规则都适用于ptr变换。这等效于要求装箱的thisType
必须是method
所属类的子类。
我认为您会犯规:“ 这等效于要求装箱的thisType
必须是method
所属类的子类”。 / p>
在您的情况下,
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 指针,该指针是该类的实例。这对于引用类型是有意义的,因为它们具有标识并且 this 指针表示该标识。但是,值类型仅在装箱时才具有标识。为了解决此问题,值类型的非静态方法上的 this 指针是值类型的byref参数,而不是普通的按值参数。
可以通过以下方式调用值类型的非静态方法:
- 对于值类型的未装箱实例,确切的类型是静态已知的。
call
指令可用于调用该函数,并将实例的地址作为第一个参数( this 指针)传递。调用指令所使用的元数据令牌应将值类型本身指定为方法的类。- 考虑到值类型的带框实例,需要考虑以下三种情况:
- 在值类型本身上引入的实例或虚拟方法:解开实例的框,并使用值类型作为方法的类直接调用该方法。
- 从基类继承的虚拟方法:使用
callvirt
指令并根据需要在System.Object
,System.ValueType
或System.Enum
类上指定方法。- 由值类型实现的接口上的虚拟方法:使用
callvirt
指令并在接口类型上指定方法。
您直接在值类型上调用方法(而不是在值框上),因此应使用call
。