Поместите объект поверх стека в ILGenerator - PullRequest
3 голосов
/ 14 февраля 2011

Я должен передать функции экземпляр объекта, поэтому очевидно, что вся информация, которая будет принята в качестве аргумента, должна быть загружена в стек оценки. Вот код, который я ищу

someClass SomeObject = new someClass();

il.Emit(OpCodes.LoadObject, SomeObject);
il.Emit(OpCodes.CallVirt, MethodInfo Function);


public void Function(Object obj)
{
       Type type = typeof(obj);
       //do something w.r.t to the type
}

Мне не требуется никакой информации, хранящейся в классе, только тип, и я не могу использовать ни один из примитивных типов, чтобы принять решение по

В заключение я прочитал, что могу использовать указатель для загрузки типа с использованием некоторых кодов операций.... но я полностью потерян здесь, любая помощь или указатели в правильном направлении были бы великолепны:)

[ОБНОВЛЕНИЕ]

Хорошоя нашел ответ на свой вопрос, попробовал его, и он работает, не знаю, правильный ли это путь или нет, но я могу успешно создать и загрузить объект в стек и передать его функции

ConstructorInfo ci = typeof(SomeClass).GetConstructor(System.Type.EmptyTypes);
IL.Emit(OpCodes.Newobj, ci);
IL.Emit(OpCodes.Call, SomeFunctionMethodInfo);

SomeFunctionMethodInfo - это функция, которая принимает Object в качестве аргумента, я успешно передал объект в функцию и также могу манипулировать им и возвращать класс как объект.

Нигде я не мог найти ссылку на этот пример, просто выяснил это через MSDN, я делаю что-то не так или есть какие-то недостатки?Эксперты, пожалуйста, если вы могли бы исправить это или дать лучший ответ

Ответы [ 4 ]

4 голосов
/ 14 февраля 2011

Вы не можете извлечь ссылку из воздуха в IL, если только вы не закодируете ссылку как литерал IntPtr, в этом случае:
a.не делай этого
б.вам нужно закрепить и
c.не делайте этого.

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

Разница (отсюда и мое предпочтение) состоит в том, что первое требует (самое большее) параметр object[] в методе - и вы можете использовать DynamicMethod;вторая требует MethodBuilder, TypeBuilder, ModuleBuilder, AssemblyBuilder и т. д. и, следовательно, требует больше работы.

Причина, о которой я упоминаю object[], заключается в том, что обычно вам нужна общая подпись для сгенерированных методов, даже если они требуют разных входных данных.Это позволяет вам связываться с фиксированным типом делегата и использовать более быстрое выполнение Invoke (DynamicInvoke медленно).

Например:

class SomeType { }
delegate void SomeDelegateType(params object[] args);
public class Program
{
    public static void Main()
    {
        var dn = new DynamicMethod("foo", (Type)null, new[] {typeof(object[])});
        var il = dn.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ldelem_Ref);
        il.EmitCall(OpCodes.Call, typeof(Program).GetMethod("Function"), null);
        il.Emit(OpCodes.Ret);
        var action = (SomeDelegateType)dn.CreateDelegate(typeof(SomeDelegateType));

        var obj = new SomeType();
        action(obj);
    }
    public static void Function(object obj)
    {
        Type type = obj.GetType();
        Console.WriteLine(type);
    }
}

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

object someObj = ...
Action action = () => Function(someObj);

Это создается как:

class <>somehorriblename {
    public object someObj;
    public void SomeGeneratedName() { Function(someObj); }
}
...
var captureClass = new <>somehorriblename();
captureClass.someObj = ...
Action action = captureClass.SomeGeneratedName;
3 голосов
/ 28 февраля 2016

Один простой метод, который я использовал, - это получение GCHandle , затем получение IntPtr (статическим методом GCHandle.ToIntPtr ), а затем преобразование его в long или integer (используя ToPointer или ToInt64 ).

Так я смог позвонить ILGenerator.Emit(OpCodes.Ldc_I8, ptr).

1 голос
/ 08 января 2018

Вот полная реализация решения, описанного другими на этой странице здесь и здесь .Этот код позволяет вам импортировать или «жестко кодировать» любую ссылку на живой объект, которую вы можете предоставить, в поток IL DynamicMethod как постоянно записанный 32- или 64-битный литерал.

Обратите внимание, что это явно не рекомендуемая методика, и она показана здесь только в образовательных и / или экспериментальных целях

Какотмеченный в одном из комментариев , вам не нужно пин GCHandle;для GC вполне нормально перемещать объект как обычно, поскольку числовое значение дескриптора не изменится, пока экземпляр остается живым.Реальная причина, по которой вам нужен GCHandle, заключается в том, что завершенный экземпляр DynamicMethod будет не содержать ссылку на встроенный дескриптор (или даже не знать о нем)внутри себя.Без GCHandle, экземпляр можно было бы собрать , когда / если все другие ссылки на него выходят за рамки.

Этот код ниже использует слишком осторожный подход, преднамеренно отказываясь от GCHandlestruct (то есть, не освобождая ее) после использования для извлечения ссылки на объект.Это означает, что целевой экземпляр никогда не будет собран.Если у вас есть специальные знания о вашем конкретном приложении, которое позволяет вам это делать, не стесняйтесь использовать некоторые другие средства, гарантирующие, что цель выживет и / / все конкретные DynamicMethod, в которые она излучается с помощью этой техники;в этом случае вы можете освободить GCHandle (код, показанный закомментированным) после его использования для получения значения дескриптора.

/// <summary>
/// Burn an reference to the specified runtime object instance into the DynamicMethod
/// </summary>
public static void Emit_LdInst<TInst>(this ILGenerator il, TInst inst)
    where TInst : class
{
    var gch = GCHandle.Alloc(inst);

    var ptr = GCHandle.ToIntPtr(gch);

    if (IntPtr.Size == 4)
        il.Emit(OpCodes.Ldc_I4, ptr.ToInt32());
    else
        il.Emit(OpCodes.Ldc_I8, ptr.ToInt64());

    il.Emit(OpCodes.Ldobj, typeof(TInst));

    /// Do this only if you can otherwise ensure that 'inst' outlives the DynamicMethod
    // gch.Free();
}

Вклад этого ответа, не упомянутого другими, заключается в том, что вы должны использовать Opcodes.Ldobj инструкция для принудительного приведения правильного времени выполнения Type в новый жестко запрограммированный литерал, как показано выше.Легко убедиться, что это хорошая практика, с помощью следующей последовательности испытаний.Он выдает bool, указывающий, соответствует ли System.Type только что импортированного экземпляра тому, что мы ожидаем, и возвращает true, только когда в методе расширения, показанном выше, присутствует инструкция Opcodes.Ldobj is.

TInst _inst = new MyObject();

// ...
il.Emit_LdInst(_inst);                  //  <-- the function shown above
il.Emit(OpCodes.Isinst, typeof(TInst));
il.Emit(OpCodes.Ldnull);
il.Emit(OpCodes.Ceq);
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Xor);

То, что не кажется необходимым после нашего грубого Ldc_I4 / Ldc_I8 пуша Conv_I, и это похоже на правду, даже если мы отбросим IntPtr.Size проверка и просто используйте Ldc_I8, чтобы всегда загружать long, даже на x86.Это снова благодаря Opcodes.Ldobj сглаживанию таких нарушений.

Вот еще один пример использования.Он проверяет равенство ссылок между встроенным экземпляром (импортированным во время создания DynamicMethod) и любыми объектами ссылочного типа, которые могут быть по-разному предоставлены при вызове этого метода в любое время в будущем.Возвращает true только когда появляется его давно потерянный прародитель.(Интересно, как могло бы произойти воссоединение ...)

il.Emit_LdInst(cmp);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ceq);

Наконец, замечание об ограничении where TInst : class на метод расширения, показанное вверху.Во-первых, нет смысла импортировать тип значения таким образом, поскольку вместо этого вы можете просто импортировать его поля как литералы.Вы, возможно, заметили, однако, что решающее значение Opcodes.Ldobj, которое делает импорт более надежным, задокументировано как предназначенное для типов значений, а не ссылочных типов, как мы делаем здесь.Простой ключ к этому - помнить, что на самом деле ссылка на объект - это дескриптор, который сам по себе является просто 32- или 64-битным шаблоном, который всегда копируется по значению.Другими словами, в основном ValueType.

Я довольно широко протестировал все это на x86 и x64 , отладке и выпуске, и это работаетотлично пока без проблем.

1 голос
/ 08 января 2018

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

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

Если вам вместо этого нужно разместить неизвестное количество произвольных экземпляров стороннего типа во время выполнения или если вы не можете исключить их произвольную задержку - любая ситуация, кажется, влечет за собой какое-то соглашение для сохранения их всех прямыми - вы все равно можете опубликовать они глобально, в данном случае в массив Object[] (или другого типа), и каким-то заранее установленным способом, который понимается под кодом IL.

Как уже отмечалось, вероятно, должен был бы быть какой-то способ координировать издательскую деятельность (активизируемую некоторым соответствующим «модулем управления» в вашей системе) с более поздним потреблением (настраиваемым IL, также вашим, но предположительно подчиненным для ограничения подписи метода), так что IL сможет различать и выбирать соответствующий экземпляр из опубликованного массива, основываясь исключительно на аргументах - или любых других доказательствах - он делает фактически имеет доступ или действительно получает в своем (предположительно ограниченном) списке аргументов.

В зависимости от ситуации вам может потребоваться выбрать способ создания единого списка опубликованных экземпляров. Во всех случаях ожидается, что потребитель IL никогда не изменяет опубликованные записи, но один из факторов, который следует учитывать, - требуется ли вам несколько издателей или нет. Опции включают в себя:

  • Предварительно заполнить массив readonly всеми ожидаемыми сторонними экземплярами только один раз (т. Е. Во время инициализации), гарантируя, что это до возможности доступа любого из ваших пользовательских IL. Очевидно, это самый простой план.
  • Использовать монотонно растущий массив (внешние экземпляры добавляются, но не удаляются в течение жизни AppDomain). Это упрощает координацию с IL, потому что индексы в массиве после их выдачи никогда не истекут и не изменятся. Это также позволяет создавать IL как «установил и забыл», что означает, что при создании у каждого экземпляра DynamicMethod может быть непосредственно записан соответствующий индекс массива. Публикация на неограниченный срок приносит выгоды обеим сторонам: DynamicMethod во время выполнения в принципе не может быть сожженным IL, предпочитая этот режим настройки везде, где это возможно, в то время как монотонность списка означает, что издателю / менеджеру не нужно сохранять ни созданные DynamicMethod идентификаторы, ни все, что связано с тем, какой из экземпляров стороннего объекта он публикует.
  • Простота стратегии «установил и забыл» особенно эффективна в сценариях, где каждому DynamicMethod выдается собственный или отдельный собственный экземпляр.
  • Если для какого-либо из перечисленных здесь динамических сценариев требуется безопасность потоков (т. Е. Из-за наличия нескольких менеджеров или источников публикации), это достигается тривиально с помощью метода безблокировочного параллелизма каждого издателя всегда использовать Interlocked.Exchange (с защитным SpinWait) для замены новой, но строго расширенной версии предыдущего опубликованного массива.
  • Если инородные экземпляры действительно и обязательно многочисленны и эфемерны, то вы можете быть вынуждены перейти к более сложному подходу, когда ваш опубликованный список их корректируется динамически. В этом случае экземпляры стороннего объекта присутствуют в вашем списке только на временной основе, и для координации с кодом IL может потребоваться использование более сложных методов сигнализации или связи.
  • Нетэто означает, что даже если это не является принципиально необходимым, вы все равно можете выбрать более сложную схему управления «на лету», если сторонние экземпляры являются дорогостоящими и если сохранение экземпляров с истекшим сроком действия в вашем массиве является единственной ссылкой GC, которая предотвращение освобождения и восстановления таких ресурсов.
...