Эквивалентный из загрузчиков класса в.NET

2 ответа

Ответ да, но решение немного хитро.

System.Reflection.Emit пространство имен определяет типы, который позволяет блокам быть сгенерированными динамично. Они также позволяют сгенерированным блокам быть определенными инкрементно. Другими словами, возможно добавить, что типы к динамическому блоку, чтобы выполнить сгенерированный код, и затем последний добавляют больше типов к блоку.

System.AppDomain класс также определяет событие AssemblyResolve, которое стреляет каждый раз, когда платформе не удается загрузить блок. Путем добавления обработчика для того события возможно определить единственный блок "во время выполнения", в который помещаются все "созданные" типы. Код, сгенерированный компилятором, который использует созданный тип, относился бы к типу в блоке во время выполнения. Поскольку блок во время выполнения на самом деле не существует на диске, , событие AssemblyResolve было бы запущено в первый раз, когда скомпилированный код пытался получить доступ к созданному типу. Дескриптор для события тогда генерировал бы динамический блок и возвратил бы его CLR.

, К сожалению, существует несколько хитрых точек к тому, чтобы заставлять это работать. Первая проблема гарантирует, что обработчик событий будет всегда устанавливаться, прежде чем скомпилированный код выполняется. С консольным приложением это легко. Код к сцеплению обработчик событий может просто быть добавлен к Main метод перед другими выполнениями кода. Для библиотек классов, однако, нет никакого основного метода. dll может быть загружен как часть приложения, записанного на другом языке, таким образом, не действительно возможно предположить, что всегда существует основной метод, доступный сцеплению код обработчика событий.

вторая проблема гарантирует, что типы, на которые ссылаются, все введены в динамический блок перед любым кодом, что ссылки их используются. System.AppDomain класс также определяет TypeResolve событие, которое выполняется каждый раз, когда CLR неспособен разрешить тип в динамическом блоке. Это дает обработчику событий возможность определить тип в динамическом блоке перед кодом, который использует его выполнения. Однако то событие не будет работать в этом случае. CLR не уволит событие за блоки, на которые "статически ссылаются" другие блоки, даже если блок, на который ссылаются, определяется динамично. Это означает, что нам нужен способ выполнить код, прежде чем любой другой код в скомпилированных выполнениях блока и иметь его динамично введет типы, в которых он нуждается в блок во время выполнения, если они не были уже определены. Иначе, когда CLR пытался загрузить те типы, он заметит, что динамический блок не содержит типы, в которых они нуждаются и выдадут исключение загрузки типа.

, К счастью, CLR предлагает решение обеих проблем: Инициализаторы Модуля. Инициализатор модуля является эквивалентом "статического конструктора класса", за исключением того, что это инициализирует весь модуль, не только единый класс. Baiscally, CLR будет:

  1. Выполнение конструктор модуля, прежде чем к любым типам в модуле получают доступ.
  2. Гарантия, что только те типы, к которым непосредственно получает доступ конструктор модуля, будут загружены, в то время как это выполняется
  3. Не, позволяют коду вне модуля получать доступ к любому из, его - участники, пока конструктор не закончил.

Это делает это для всех блоков, и включая библиотеки классов и включая исполняемые файлы, и для EXEs выполнит конструктора модуля прежде, чем выполнить Основной метод.

Посмотрите этот сообщение в блоге для получения дополнительной информации о конструкторах.

В любом случае, полное решение моей проблемы требует нескольких частей:

  1. следующее определение класса, определенное во "времени выполнения языка dll", на который ссылаются все блоки, произведенные компилятором (это - код C#).

    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Reflection.Emit;
    
    namespace SharedLib
    {
        public class Loader
        {
            private Loader(ModuleBuilder dynamicModule)
            {
                m_dynamicModule = dynamicModule;
                m_definedTypes = new HashSet<string>();
            }
    
            private static readonly Loader m_instance;
            private readonly ModuleBuilder m_dynamicModule;
            private readonly HashSet<string> m_definedTypes;
    
            static Loader()
            {
                var name = new AssemblyName("$Runtime");
                var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
                var module = assemblyBuilder.DefineDynamicModule("$Runtime");
                m_instance = new Loader(module);
                AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            }
    
            static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
            {
                if (args.Name == Instance.m_dynamicModule.Assembly.FullName)
                {
                    return Instance.m_dynamicModule.Assembly;
                }
                else
                {
                    return null;
                }
            }
    
            public static Loader Instance
            {
                get
                {
                    return m_instance;
                }
            }
    
            public bool IsDefined(string name)
            {
                return m_definedTypes.Contains(name);
            }
    
            public TypeBuilder DefineType(string name)
            {
                //in a real system we would not expose the type builder.
                //instead a AST for the type would be passed in, and we would just create it.
                var type = m_dynamicModule.DefineType(name, TypeAttributes.Public);
                m_definedTypes.Add(name);
                return type;
            }
        }
    }
    

    класс определяет одиночный элемент, который содержит ссылку на динамический блок, в котором будут созданы созданные типы. Это также содержит "набор хеша", который хранит набор типов, которые были уже динамично сгенерированы, и наконец определяет участника, который может использоваться для определения типа. Этот пример просто возвращает Систему. Отражение. Испустить. Экземпляр TypeBuilder, который может тогда использоваться для определения сгенерированного класса. В реальной системе метод, вероятно, взял бы в представлении AST класса, и просто сделал бы поколение, которое это сам.

  2. Скомпилированные блоки, которые испускают следующие две ссылки (показанный в синтаксисе ILASM):

    .assembly extern $Runtime
    {
        .ver 0:0:0:0
    }
    .assembly extern SharedLib
    {
        .ver 1:0:0:0
    }
    

    Здесь "SharedLib" является предопределенной библиотекой времени выполнения Языка, которая включает класс "Загрузчика", определенный выше, и "$Runtime" является динамическим блоком во время выполнения, в который будут вставлены типы consructed.

  3. А "конструктор модуля" в каждом блоке скомпилирован на языке.

    , Насколько я знаю, нет никаких языков.NET, которые позволяют Конструкторам Модуля быть определенными в источнике. C++ / компилятор CLI является единственным компилятором, который я знаю этого, генерирует их. В IL они похожи на это, определенное непосредственно в модуле и не в любых определениях типа:

    .method privatescope specialname rtspecialname static 
            void  .cctor() cil managed
    {
        //generate any constructed types dynamically here...
    }
    

    Для меня, Это не проблема, что я должен записать пользовательский IL, чтобы заставить это работать. Я пишу компилятор, таким образом, генерация кода не является проблемой.

    В случае блока, который использовал типы tuple<i as int, j as int> и tuple<x as double, y as double, z as double>, конструктор модуля должен будет генерировать типы как следующее (здесь в синтаксисе C#):

    class Tuple_i_j<T, R>
    {
        public T i;
        public R j;
    }
    
    class Tuple_x_y_z<T, R, S>
    {
        public T x;
        public R y;
        public S z;
    }
    

    классы кортежа сгенерированы как универсальные типы для обхождения проблем доступности. Это позволило бы коду в скомпилированном блоке использовать tuple<x as Foo>, где Foo был некоторым непубличным типом.

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

    var loader = SharedLib.Loader.Instance;
    lock (loader)
    {
        if (! loader.IsDefined("$Tuple_i_j"))
        {
            //create the type.
            var Tuple_i_j = loader.DefineType("$Tuple_i_j");
            //define the generic parameters <T,R>
           var genericParams = Tuple_i_j.DefineGenericParameters("T", "R");
           var T = genericParams[0];
           var R = genericParams[1];
           //define the field i
           var fieldX = Tuple_i_j.DefineField("i", T, FieldAttributes.Public);
           //define the field j
           var fieldY = Tuple_i_j.DefineField("j", R, FieldAttributes.Public);
           //create the default constructor.
           var constructor= Tuple_i_j.DefineDefaultConstructor(MethodAttributes.Public);
    
           //"close" the type so that it can be used by executing code.
           Tuple_i_j.CreateType();
        }
    }
    

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

кто-либо знает о более легком способе сделать это?

52
ответ дан Mat 26 November 2019 в 22:19
поделиться

Я думаю, что это - тип вещи, которую ДОЛЛАР, как предполагается, обеспечивает в C# 4.0. Довольно трудно для прибытия информацией все же но возможно мы узнаем больше в PDC08. Нетерпеливо ожидая для наблюдения решения C# 3, хотя... Я предполагаю, что это использует анонимные типы.

-5
ответ дан Kevin Dostalek 26 November 2019 в 22:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: