habrahabr

Reflection.Emit: инициализация массива

  • пятница, 2 января 2015 г. в 02:10:48
http://habrahabr.ru/post/247201/

Логичным дополнением к прошлой статье был-бы наглядный пример, хотя-бы для того, чтобы показать что не так страшен чёрт, как его рисуют, и на самом деле даже если вам необходимо собрать инициализатор массива через Reflection.Emit, то большинство лишних телодвижений возьмёт на себя API, а от вас остаётся, по большей части, только слизать придуманный компилятором код из прошлой статьи. В этом примере я ограничусь простым статическим массивом на 3 System.Int32 элемента.
Ну а начнём мы с того, с чего начинается едва ли не каждый пост про Reflection.Emit — создание динамической сборки, модуля и типа:

TypeBuilder l_typeBuilder = AppDomain.CurrentDomain.
                                        DefineDynamicAssembly
                                            (
                                                new System.Reflection.AssemblyName("Reflection.Emit array initilization example"), 
                                                System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave
                                            ).//новая динамическая сборка
                                        DefineDynamicModule("Example").//обьявление модуля
                                        DefineType("ExampleClass");//обьявление типа

Вот и готов TypeBuilder. Одним из странных моментов прошлой статьи были вложенный тип с директивой .size и соответствующее этому типу поле. Вам не придётся об этом заботиться, ибо над этим уже подумали в Microsoft: метод TypeBuilder.DefineInitializedData придумает и класс, и поле, запишет данные куда надо, и придумает всю мишуру, которая только может им пригодиться. Это, кстати, следующий шаг:

FieldBuilder l_fieldBuilder = l_typeBuilder.DefineInitializedData("initialization_data", new byte[]{ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, System.Reflection.FieldAttributes.Static);

Я не стал заморачиваться с названием поля и обозвал его так, как обозвал. Как и должно быть, данными этого массива являются три числа (1,2,3) в little endian. Т.к. целевой массив статический, то и атрибут стоит соответствующий (Стоит заметить что это всё ещё не наш целевой массив). Обьявление целевого поля будет, думаю, многим уже знакомо:

FieldBuilder l_fieldBuilder2 = l_typeBuilder.DefineField("m_array", typeof(int[]), System.Reflection.FieldAttributes.Static | System.Reflection.FieldAttributes.Public);

Поле как поле, ничего не обычного: статическое и публичное. Но как и любое послушное (а может, и непослушное тоже) статическое поле, оно должно инициализироваться в статическом конструкторе. Посему, пришло время создавать тот самый статический конструктор. На этот момент у MS тоже заготовлена загогулина:
ConstructorBuilder l_cb = l_typeBuilder.DefineTypeInitializer();

После этого остаётся только создать ILGenerator и написать тело метода. Код аналогичен коду из прошлой статьи, за исключенем того, что используются опкоды для статических полей:

ILGenerator l_propGetILGen = l_cb.GetILGenerator();
l_propGetILGen.Emit(OpCodes.Ldc_I4, 3);//отправляем размер массива в стек
l_propGetILGen.Emit(OpCodes.Newarr, typeof(int));//и создаём массив, размером в 3 элемента
l_propGetILGen.Emit(OpCodes.Dup);//дублируем ссылку. InitializeArray возвращает void, так что если этого не сделать, то ссылка на созданный массив просто пропадёт
l_propGetILGen.Emit(OpCodes.Ldtoken, l_fieldBuilder);//закидываем в стек токен инициализируемого поля. Поле хранит в себе RVA своего сегмента данных
l_propGetILGen.EmitCall(OpCodes.Call, new System.Action<Array, RuntimeFieldHandle>(System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray).Method, new Type[] { });//и записываем данные в поле (всё ещё не целевое)
l_propGetILGen.Emit(OpCodes.Stsfld, l_fieldBuilder2); // закидываем значение из стека(массив) в наше целевое статическое поле
l_propGetILGen.Emit(OpCodes.Ret);

Вот и всё. Ответ на вопрос «Для чего два массива?» скорее всего должен быть такой — для обеспечения потокобезопасности, значение целевого массива должно быть изменено атомарной операцией.
Полный код:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection.Emit;

namespace ReflectionEmitArrayInitializerExample
{
    class Program
    {
        static void Main(string[] args)
        {
            TypeBuilder l_typeBuilder = AppDomain.CurrentDomain.
                                        DefineDynamicAssembly
                                            (
                                                new System.Reflection.AssemblyName("Reflection.Emit array initilization example"), 
                                                System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave
                                            ).
                                        DefineDynamicModule("Example").
                                        DefineType("ExampleClass");
            FieldBuilder l_fieldBuilder = l_typeBuilder.DefineInitializedData("initialization_data", new byte[]{ 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }, System.Reflection.FieldAttributes.Static);
            FieldBuilder l_fieldBuilder2 = l_typeBuilder.DefineField("m_array", typeof(int[]), System.Reflection.FieldAttributes.Static | System.Reflection.FieldAttributes.Public);
            ConstructorBuilder l_cb = l_typeBuilder.DefineTypeInitializer();
            ILGenerator l_propGetILGen = l_cb.GetILGenerator();
            l_propGetILGen.Emit(OpCodes.Ldc_I4, 3);
            l_propGetILGen.Emit(OpCodes.Newarr, typeof(int));
            l_propGetILGen.Emit(OpCodes.Dup);
            l_propGetILGen.Emit(OpCodes.Ldtoken, l_fieldBuilder);
            l_propGetILGen.EmitCall(OpCodes.Call, new System.Action<Array, RuntimeFieldHandle>(System.Runtime.CompilerServices.RuntimeHelpers.InitializeArray).Method, new Type[] { });
            l_propGetILGen.Emit(OpCodes.Stsfld, l_fieldBuilder2); 
            l_propGetILGen.Emit(OpCodes.Ret);
            System.Type l_t = null;
            System.Reflection.FieldInfo l_pi = (l_t = l_typeBuilder.CreateType()).GetField("m_array");
            int[] l_array = (int[])l_pi.GetValue(null);
        }
    }
}


l_array станет массивом с тремя элементами {1,2,3}.