【发布时间】:2015-08-13 06:17:31
【问题描述】:
在使用 IL 进行一些实验期间,我尝试将程序集中的 callvirt 调用更改为 call 方法。基本上发生的事情是我有一个带有我正在调用的成员函数的继承链。
基本上调用是这样的:
((MyDerivedClass)myBaseObject).SomeCall();
或在 IL
castclass MyDerivedClass // ** 1
call SomeCall() // ** 2
基类定义SomeCall为抽象方法,派生类实现它。派生类是密封的。
我知道callvirt 基本上相当于检查对象是否为空,如果它没有使用vtable 调用方法,如果是,则抛出异常。就我而言,我知道它永远不会是null,而且我知道那是我想要调用的实现。我理解为什么在这种情况下您通常需要callvirt。
也就是说,因为我知道对象永远不会为空,并且始终是派生类型的实例,所以我认为这不是问题:
- 当您考虑将数据和类型分开时,我实际上认为 (**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
我不确定这里发生了什么……在我看来它很相似。
【问题讨论】:
-
peverify 是否提供特定信息?您能否提供一个简短但完整的 IL 程序来演示该问题?
-
我在一个更大的程序中遇到了它。让我看看我是否可以编写一个最小的测试用例。
-
@JonSkeet 我尽了最大的努力,但到目前为止没有任何运气在最小的测试用例场景中重现该问题。更奇怪的是,最小测试用例的代码和IMO几乎一模一样。
-
我不确定您为什么在这里使用 C# - 为什么不只显示无法验证的 IL?我只是编译一个小 C# 程序,然后用 ildasm 将其反编译为 IL,修改 IL,用 ilasm 重新组装,然后对其进行 peverify...
-
@JonSkeet 我添加了 Value 的 IL 代码。这就是其中的一切。至于 C# 代码,我只是把它放在一边……现在真的不重要了,不是吗?