在一些 IL 实验中,我尝试将callvirt
程序集中的调用更改为call
方法。基本上发生的事情是我有一个带有我正在调用的成员函数的继承链。
基本上调用是这样的:
((MyDerivedClass)myBaseObject).SomeCall();
或在 IL
castclass MyDerivedClass // ** 1
call SomeCall() // ** 2
基类定义SomeCall
为抽象方法,派生类实现它。派生类是密封的。
我知道这callvirt
基本上相当于检查对象是否为空,如果它没有使用 vtable 调用方法,如果是,则抛出异常。就我而言,我知道它永远不会null
,我知道那是我想要调用的实现。callvirt
我理解为什么在这种情况下您通常需要 a 。
也就是说,因为我知道对象永远不会为空,并且始终是派生类型的实例,所以我认为这不是问题:
- 当您考虑将数据和类型分开时,我实际上认为 (**1) 可以删除(对象的数据将相同)并且
- 那 (**2) 可以是一个简单的
call
,因为我们确切地知道要调用哪个成员。不需要 vtable 查找。
在我看来,编译器在某些情况下也可以推断出相当合理的事情。对于那些感兴趣的人,是的callvirt
,虽然速度非常小,但速度会受到惩罚。
然而。PEVerify 告诉我这是错误的。作为一个好孩子,我总是注意 PEVerify 告诉我的内容。那么我在这里错过了什么?为什么更改此调用会导致不正确的程序集?
显然创建一个最小的测试用例并不是那么简单......到目前为止,我没有太多运气。
至于问题本身,我可以简单地在一个更大的程序中重现它:
[IL]: Error: [C:\tmp\emit\test.dll : NubiloSoft.Test::Value][offset 0x00000007] The 'this' parameter to the call must be the calling method's 'this' parameter.
值的 IL 代码:
L_0000: ldarg.0
L_0001: ldfld class NubiloSoft.Test SomeField
L_0006: ldarg.1
L_0007: call instance bool NubiloSoft.Test::Contains(int32)
该字段的类型是NubiloSoft.Test
。
至于Contains
,它在基类中是抽象的,而在派生类中则被覆盖。正如你所期望的那样。当我删除“抽象基础方法”+“覆盖”时,PEVerify 再次喜欢它。
为了重现这个问题,我这样做了,到目前为止没有运气在一个最小的测试用例中重现它:
public abstract class FooBase
{
public abstract void MyMethod();
}
// sealed doesn't seem to do anything...
public class FooDerived : FooBase
{
public override void MyMethod()
{
Console.WriteLine("Hello world!");
}
}
public class FooGenerator
{
static void Main(string[] args)
{
Type t = CreateClass();
object o = Activator.CreateInstance(t, new[] { new FooDerived() });
var meth = t.GetMethod("Caller");
meth.Invoke(o, new object[0]);
Console.ReadLine();
}
public static Type CreateClass()
{
// Create assembly
var assemblyName = new AssemblyName("testemit");
var assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName,
AssemblyBuilderAccess.RunAndSave, @"c:\tmp");
// Create module
var moduleBuilder = assemblyBuilder.DefineDynamicModule("testemit", "test_emit.dll", false);
// Create type : IFoo
var typeBuilder = moduleBuilder.DefineType("TestClass", TypeAttributes.Public, typeof(object));
// Apparently we need a field to trigger the issue???
var field = typeBuilder.DefineField("MyObject", typeof(FooDerived), FieldAttributes.Public);
ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig |
MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
CallingConventions.HasThis, new Type[] { typeof(FooDerived) });
// Generate the constructor IL.
ILGenerator gen = constructorBuilder.GetILGenerator();
// The constructor calls the constructor of Object
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
// Store the field
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Stfld, field);
// Return
gen.Emit(OpCodes.Ret);
// Add the 'Second' method
var mb = typeBuilder.DefineMethod("Caller",
MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual |
MethodAttributes.Final | MethodAttributes.Public, CallingConventions.HasThis,
typeof(void), Type.EmptyTypes);
// Implement
gen = mb.GetILGenerator();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldfld, field);
gen.Emit(OpCodes.Call, typeof(FooDerived).GetMethod("MyMethod"));
gen.Emit(OpCodes.Ret);
Type result = typeBuilder.CreateType();
assemblyBuilder.Save("testemit.dll");
return result;
}
}
当您运行它并调用 peverify 时,它会告诉您代码没有错误... :-S
我不确定这里发生了什么......在我看来它很相似。