Можно ли косвенно загрузить тип значения в стек - PullRequest
7 голосов
/ 16 сентября 2008

В Microsoft IL для вызова метода с типом значения необходима косвенная ссылка. Допустим, у нас есть ILGenerator с именем «il» и что в настоящее время у нас есть 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);

Семейство инструкций ldind выглядит многообещающе (особенно ldind_ref), но я не могу найти достаточную документацию, чтобы знать, может ли это вызвать упаковку значения, что, как я подозреваю, может.

Я посмотрел на вывод компилятора C #, но он использует локальные переменные для достижения этого, что заставляет меня поверить, что первый способ может быть единственным. У кого-нибудь есть идеи получше?

**** Редактировать: Дополнительные примечания ****

Попытка прямого вызова метода, как в следующей программе с закомментированными строками, не работает (ошибка будет «Операция может дестабилизировать среду выполнения»). Раскомментируйте строки, и вы увидите, что все работает как положено, возвращая "True".

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));

Так что вы не можете просто вызвать метод со значением в стеке, потому что это тип значения (хотя вы могли бы, если бы это был ссылочный тип).

Чего я хотел бы добиться (или узнать, возможно ли это), так это заменить три строки, которые показаны закомментированными, но сохранить работоспособность программы без использования временного локального.

Ответы [ 4 ]

2 голосов
/ 17 сентября 2008

Если переменная уже находится в стеке, вы можете продолжить и просто вызвать вызов метода.

Кажется, что конструктор не помещает переменную в стек в типизированной форме. Немного покопавшись в 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, если новый объект не находится в стеке.

1 голос
/ 18 марта 2016

Я понял это! К счастью, я читал о коде операции unbox и заметил, что он выдвигает адрес значения. unbox.any толкает фактическое значение. Таким образом, чтобы вызвать метод для типа значения без необходимости сохранять его в локальной переменной и затем загрузить его адрес, вы можете просто box, а затем unbox. Используя ваш последний пример:

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));

Недостатком этого является то, что бокс вызывает выделение памяти для объекта в штучной упаковке, поэтому он немного медленнее, чем использование локальных переменных (которые уже были бы выделены). Но это избавляет вас от необходимости определять, объявлять и ссылаться на все необходимые вам локальные переменные.

1 голос
/ 18 сентября 2008

После рассмотрения вариантов и дальнейшего рассмотрения я думаю, что вы правы, предполагая, что это невозможно. Если вы изучите поведение стека инструкций MSIL, то увидите, что ни один из операндов не оставляет свои операнды в стеке. Так как это было бы требованием для операции 'get address of stack entry', я вполне уверен, что она не существует.

Это оставляет вас с dup + box или stloc + ldloca. Как вы указали, последний, вероятно, более эффективен.

@ greg: Многие инструкции оставляют свой результат в стеке, но никакие инструкции не оставляют в стеке ни одного из их операндов , что потребуется для получения адреса элемента стека Инструкция.

0 голосов
/ 04 июня 2012

Только что написал класс, который делает то, что просит OP ... вот код IL, который создает компилятор C #:

  IL_0008:  ldarg.0
  IL_0009:  ldarg.1
  IL_000a:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
  IL_000f:  stfld      valuetype [mscorlib]System.Nullable`1<int32> ConsoleApplication3.Temptress::_X
  IL_0014:  nop
  IL_0015:  ret
...