在调用程序集中的任何方法之前,CLR调用的最早入口点是什么?

在过去的几年里,我偶尔会想知道.NET世界中有哪些(in)着名的DLL_PROCESS_ATTACH可用。 我所说的任何文档,略微简化,类的最早入口点是静态构造函数(cctor),但是你不能影响它何时被调用 ,也不能定义一个保证在任何其他cctor之前被调用的cctor。或字段初始化程序,黑客,如果从未使用过类,它甚至可能根本不会被调用。

因此,如果您想要在调用程序集的任何方法之前保证已初始化某些内容,并且您不希望为程序集中的每个类添加一个cctor,您可以采取什么方法? 或者,这些年来我是否有一个简单易用的托管解决方案?

我通常不回答我自己的问题,但同时我确实找到了一个之前没有出现的答案,所以我走了。

经过一些研究,我发现了微软的这篇文章 ,它解释了在DllMain和解决方案中混合托管和非托管代码的问题,这是第二版CLI, 模块初始化器产生的 。 引用:

此初始化程序在本机DllMain之后运行(换句话说,在加载程序锁定之外),但在运行任何托管代码或从该模块访问托管数据之前运行。 模块.cctor的语义与类.cctors的语义非常相似,并在ECMA C#和公共语言基础结构标准中定义。

虽然我无法在当前的ECMA规范中找到术语模块初始化程序 ,但它在逻辑上遵循类型初始化程序和全局特殊类(参见MethodDef第22.26节,子点40)。 此function是 .NET 1.1 之后实现的(即从2.0开始)。 另请参阅此半官方说明 。

这个问题不是关于C#,而是因为它是.NET的通用语言:C#不知道全局方法,你不能创建 ,更不用说它的cctor了。 但是,Einar Egilsson已经认识到了这种明显的不足,并创建了InjectModuleInitializer.exe,允许您从Visual Studio中执行此操作作为后期/编译步骤。 在C ++。NET中,使用这种方法是微不足道的,并建议用DllMain代替。 另见Ben Voigt的这个答案 (不是接受的答案)和yoyoyoyosef的这个答案 。

简而言之, 模块初始化程序是在加载模块之后调用的第一个方法(不一定是在加载程序集时!)并且在调用任何类或实例方法之前。 它不需要任何参数,不返回任何值,但可以在其正文中包含任何托管代码。

实际上, cctor调用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(); } } } 

编辑 :还考虑使用工厂模式 ,它将帮助您在返回创建的对象之前执行所有必需的初始化。

这是设计的:它最小化静态构造函数之间的耦合。 您知道在您的类初始化之前以及您的类使用的任何类的cctors之后,将调用您的cctor。 但是,与同一应用程序中不相关的类相比,它无法保证何时运行。

如果要确保在入口点之前运行某些代码,请考虑为主应用程序编写包装器。 一种简单的方法是将它放在一个单独的可执行文件中。

一种更独立的方式可能是:

  1. 按正确的顺序运行所需的任何启动代码。 不要引用不应该初始化的程序集中的任何类型。
  2. 创建自己的应用域
  3. 在第二个应用程序域中运行真正的入口点