是否可以在堆栈上间接加载值类型?

在Microsoft IL中,要调用值类型的方法,您需要一个间接引用。 假设我们有一个名为“il”的ILGenerator,并且目前我们在堆栈顶部有一个Nullable,如果我们想检查它是否有值,那么我们可以发出以下内容:

var local = il.DeclareLocal(typeof(Nullable<int>));
il.Emit(OpCodes.Stloc, local);
il.Emit(OpCodes.Ldloca, local);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

然而,跳过将其保存为局部变量会很好,只需调用堆栈中已有变量地址的方法,如下所示:

il.Emit(/* not sure */);
var method = typeof(Nullable<int>).GetMethod("get_HasValue");
il.EmitCall(OpCodes.Call, method, null);

lind系列指令看起来很有前景(特别是ldind_ref),但我找不到足够的文档来知道这是否会导致值的装箱,我怀疑它可能。

我看了一下C#编译器的输出,但它使用局部变量来实现这一点,这让我相信第一种方式可能是唯一的方法。 任何人有更好的想法?

****编辑:其他注意事项****

试图直接调用该方法,如下面的程序中注释掉的行一样,不起作用(错误将是“操作可能会破坏运行时”)。 取消注释行,你会发现它确实按预期工作,返回“真”。

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
//var local = il.DeclareLocal(typeof(Nullable<int>));
//il.Emit(OpCodes.Stloc, local);
//il.Emit(OpCodes.Ldloca, local);
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

所以你不能简单地用堆栈中的值调用方法,因为它是一个值类型(尽管如果它是一个引用类型,你也可以)。

我想达到的目的(或者知道是否可能)是取代显示注释的三行,但保持程序正常工作,而不使用临时本地。


如果变量已经在堆栈中,则可以继续并发出方法调用。

看起来,构造函数不会以类型化的形式推送栈中的变量。 在挖掘IL之后,似乎在构建它之后有两种使用变量的方法。

您可以在调用构造函数之前加载将参考存储到评估堆栈中的变量,然后在调用构造函数之后再次加载该变量,如下所示:

DynamicMethod method = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
ILGenerator il = method.GetILGenerator();
Type nullable = typeof(Nullable<int>);
ConstructorInfo ctor = nullable.GetConstructor(new Type[] { typeof(int) });
MethodInfo getValue = nullable.GetProperty("HasValue").GetGetMethod();
LocalBuilder value = il.DeclareLocal(nullable);         

// load the variable to assign the value from the ctor to
il.Emit(OpCodes.Ldloca_S, value);
// load constructor args
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Call, ctor);
il.Emit(OpCodes.Ldloca_S, value);

il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(method.Invoke(null, null));

另一种选择是按照您所示的方式进行。 我能看到的唯一原因是ctor方法返回void,所以它们不像其他方法那样将它们的值放在堆栈上。 看起来很奇怪,如果新对象不在堆栈上,你可以调用Setloc。


在对这些选项进行更多和更深入的考虑之后,我认为你认为它不能完成是正确的。 如果您检查MSIL指令的堆栈行为,可以看到没有op在堆栈上保留它的操作数。 由于这将是“获取堆栈条目地址”操作的要求,我相信一个不存在。

这会让你看到dup + box或者stloc + ldloca。 正如你指出的那样,后者可能更有效率。

@greg:许多指令将结果留在堆栈上,但没有指令将任何操作数留在堆栈上,这是'获取堆栈元素地址'指令所需的操作数。


我想到了! 幸运的是,我正在阅读有关unbox操作码的信息,并注意到它会推送值的地址unbox.any推动实际价值。 所以,为了在一个值类型上调用一个方法,而不必将它存储在本地变量中,然后加载它的地址,你可以简单地使用boxunbox 。 用你的最后一个例子:

var m = new DynamicMethod("M", typeof(bool), Type.EmptyTypes);
var il = m.GetILGenerator();
var ctor = typeof(Nullable<int>).GetConstructor(new[] { typeof(int) });
il.Emit(OpCodes.Ldc_I4_6);
il.Emit(OpCodes.Newobj, ctor);
il.Emit(OpCodes.Box, typeof(Nullable<int>)); // box followed by unbox
il.Emit(OpCodes.Unbox, typeof(Nullable<int>));
var getValue = typeof(Nullable<int>).GetMethod("get_HasValue");
il.Emit(OpCodes.Call, getValue);
il.Emit(OpCodes.Ret);
Console.WriteLine(m.Invoke(null, null));

这样做的缺点是装箱会导致盒装对象的内存分配,所以比使用局部变量(已经分配的)要慢一些。 但是,它使您无需确定,声明和引用所需的所有局部变量。

链接地址: http://www.djcxy.com/p/41763.html

上一篇: Is it possible to indirectly load a value type on the stack

下一篇: Python ReportLab use of splitfirst/splitlast