Какова самая ранняя точка входа, которую CLR вызывает перед вызовом любого метода в сборке?

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

Итак, если вы хотите гарантировать что-то инициализированное до того, как будет вызван какой-либо метод вашей сборки, и вы не хотите, чтобы нужно было добавлять cctor для каждого класса в вашей сборке, какой подход вы можете предпринять? Или есть простое, управляемое решение в .NET, которое я пропустил все эти годы?

Ответ 1

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

После некоторого исследования я столкнулся с этот пост от Microsoft, в котором объясняются проблемы смешивания управляемого и неуправляемого кода внутри DllMain и решение, появившееся со второй версией CLI, инициализаторы модулей. Цитата:

Этот инициализатор запускается сразу после родной DllMain (другими словами, вне блокировки загрузчика), но до управляемый код запускается или управляемые данные доступ к нему из этого модуля. семантика модуля .cctor очень похож на класс класса .cctors и определены в ECMA С# и Общая языковая инфраструктура Стандарты.

Пока я не смог найти термин инициализатор модуля внутри текущей спецификации ECMA, он логически вытекает из инициализатора типа и глобального класса <Module> (см. раздел 22.26 на MethodDef, подпункт 40). Эта функция была реализована после .NET 1.1 (т.е. Начиная с версии 2.0). См. Также это полуофициальное описание.

Этот вопрос касался не С#, а потому, что это lingua franca.NET: С# не знает глобальных методов, и вы не можете создать <Module>, не говоря уже о его cctor. Тем не менее, Einar Egilsson распознал этот очевидный недостаток и создал InjectModuleInitializer.exe, который позволяет вам делать это как шаг post/compile из Visual Studio. В С++.NET использование этого метода тривиально и рекомендуется вместо DllMain. См. Также этот ответ от Ben Voigt (а не принятый ответ), и этот SO ответ на yoyoyoyosef.

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

Ответ 2

На самом деле не совсем верно, что cctor вызывается первым. Если у вас есть статическое поле, инициализированное статическим методом, будет вызван статический метод.

Посмотрите на этот код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CallSequence
{
    internal class Test
    {
        internal Test()
        {
            Console.WriteLine("non-static constructor");
        }

        static Test()
        {
            Console.WriteLine("static constructor");
        }

        static int myField = InitMyField();

        static int InitMyField()
        {
            Console.WriteLine("static method : (InitMyField)");
            return 0;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test();
        }
    }
}

EDIT. Также рассмотрите возможность использования Factory pattern, который поможет вам выполнить всю необходимую инициализацию перед возвратом созданный объект.

Ответ 3

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

Если вы хотите убедиться, что какой-то ваш код работает до точки входа, подумайте над написанием обертки для основного приложения. Прямым способом было бы поместить его в отдельный исполняемый файл.

Более автономный способ сделать это может быть:

  • Запустите любой код запуска, в правильном порядке. Не ссылайтесь на какие-либо типы в сборках, которые не должны инициализироваться.
  • Создайте свой собственный домен приложений
  • Запустите реальную точку входа в этом втором домене приложения