在IL Emit中循环添加对象到列表 - 公共语言运行时检测到无效程序

3
以下是我的C#代码:
List<int> list = new List<int>();
for(int Count = 0; Count < 5; Count++)
    list.Add(Count);
return list;

我的相应的发射代码如下:
LocalBuilder list = ILout.DeclareLocal(typeof(List<int>));
LocalBuilder Count = ILout.DeclareLocal(typeof(int));
LocalBuilder CmpRes = ILout.DeclareLocal(typeof(bool));
ConstructorInfo DictConstrctor = typeof(List<int>).GetConstructor(new Type[] { });
MethodInfo methodinfo_add = typeof(List<int>).GetMethod("Add", new[] { typeof(int) });
Label IL_001C = ILout.DefineLabel();
Label IL_000B = ILout.DefineLabel();

ILout.Emit(OpCodes.Newobj, DictConstrctor);
ILout.Emit(OpCodes.Stloc_0, list);
ILout.Emit(OpCodes.Ldc_I4_0);
ILout.Emit(OpCodes.Stloc_1, Count);

ILout.Emit(OpCodes.Br_S, IL_001C);
ILout.MarkLabel(IL_000B);
ILout.Emit(OpCodes.Ldloc_0, list);
ILout.Emit(OpCodes.Ldloc_1, Count);
ILout.Emit(OpCodes.Call, methodinfo_add);

ILout.Emit(OpCodes.Ldloc_1, Count);
ILout.Emit(OpCodes.Ldc_I4_1);
ILout.Emit(OpCodes.Add);

ILout.Emit(OpCodes.Stloc_1, Count);
ILout.MarkLabel(IL_001C);
ILout.Emit(OpCodes.Ldloc_1, Count);
ILout.Emit(OpCodes.Ldc_I4_2);
ILout.Emit(OpCodes.Clt);
ILout.Emit(OpCodes.Stloc_3, CmpRes);
ILout.Emit(OpCodes.Ldloc_3, CmpRes);
ILout.Emit(OpCodes.Brtrue_S, IL_000B);

ILout.Emit(OpCodes.Ldloc_0, list);
ILout.Emit(OpCodes.Ret);

代码抛出异常 -“Common Language Runtime 检测到一个无效程序。”。

我在这里做错了什么? 感激任何帮助。


那看起来像是循环到2,这是有意为之的吗? - Marc Gravell
如果你想使用emit,我强烈推荐Sigil库(https://www.nuget.org/packages/Sigil/)-它被**设计**成更加直观,具有使失败变得困难的API,并在您仍然管理时提供清晰的错误消息。 - Marc Gravell
谢谢!我稍后会再试一次。是的,计数为2。我尝试了太多东西,所以可能已经将计数更改为5。 - badari
我并没有说存储/加载使其无效;我是说它没有任何有用的目的。 - Marc Gravell
正如我所说:“local 3”不存在;因此Stloc_3Ldloc_3是格式不正确的。 - Marc Gravell
显示剩余5条评论
1个回答

5
ILout.Emit(OpCodes.Stloc_1, Count);

并且

ILout.Emit(OpCodes.Ldloc_1, Count);

毫无意义。如果您明确地说“使用本地1”,则不需要任何附加参数。

同样地:

ILout.Emit(OpCodes.Stloc_3, CmpRes); 
ILout.Emit(OpCodes.Ldloc_3, CmpRes);

尽管如实地说,我不确定CmpRes有任何有用的目的;没必要存储和加载 - 只需将其保留在堆栈上。
注意:如果Count是“local 1”,那么CmpRes就是“local 2”;没有“local 3”,因此Stloc_3Ldloc_3格式不正确。
并且再次在这里:
ILout.Emit(OpCodes.Ldloc_0, list);
ILout.Emit(OpCodes.Ldloc_1, Count);

--

接下来我们来看一下调用;你正在进行一个静态调用:

ILout.Emit(OpCodes.Call, methodinfo_add);

但那是一个对象的实例方法,所以应该是一个虚拟调用。

这里又出现了另一个本地错误:

ILout.Emit(OpCodes.Ldloc_1, Count);

并且在这里:

ILout.Emit(OpCodes.Stloc_1, Count);

并且这里:

ILout.Emit(OpCodes.Ldloc_0, list);

然而,我也严重怀疑这个循环(即使修复)是否能够实现您期望的功能 - 如果我理解正确,它实际上是:

var list = new List<int>();
for(int i [= 0] ; i < 2 ; i++) // note the 0 here implicit not explicit
{
    list.Add(i);
}
return list;

但那是一个对象上的实例方法,所以应该是一个虚拟调用。尽管你知道这一点很清楚,但这句话可能会误导其他人。实例方法并不一定意味着虚拟方法...他需要使用callvirt操作码,因为任何实例方法都会使用它进行this != null检查。 - Dudi Keleti
@DudiKeleti,虽然更普遍地说,这只是针对该场景的正确指令;即使它们不是虚方法(由JIT处理差异),所有主流编译器都会为对象方法发出callvirt。还有第三类“约束调用”,适用于更复杂的情况(泛型、结构体上的接口方法等)。很有趣。 - Marc Gravell
没问题。虽然如果我们更具体一点,例如在这种情况下,new MyClass.Method() 的 IL 代码会是这样的:call instance void Namespace.MyClass::Method()。但是不用太过烦恼你了 ;) - Dudi Keleti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接