在调用程序集中的任何方法之前,CLR调用的最早的入口点是什么?
在过去的几年中,我偶尔会想知道在.NET世界中什么样的(着名的) DLL_PROCESS_ATTACH
可用。 我所说的任何文档都略有简化,最早的类入口点是静态构造函数(cctor),但不能影响何时调用它,也不能定义一个保证在任何其他cctor之前被调用的cctor或者字段初始值设定项hack,如果从未使用该类,甚至可能根本不会调用它。
所以,如果你想在你的程序集的任何方法被调用之前保证某些东西已经被初始化了,并且你不想为你的程序集中的每个类添加一个cctor,你可以采取什么方法? 或者有没有一个简单的.NET管理解决方案,我错过了这么多年?
我通常不会回答我自己的问题,但同时我找到了一个以前没有提到的答案,所以我在这里。
经过一番研究,我在微软的这篇文章中发现了这个问题,它解释了在DllMain
中混合托管代码和非托管代码的问题,以及第二版CLI 模块初始化器带来的解决方案。 引用:
这个初始化器在原生DllMain(换言之,在加载器锁定之外)之后运行,但在任何托管代码运行之前运行,或者从该模块访问受管理的数据。 模块.cctor的语义与类.cctor非常相似,并且在ECMA C#和通用语言基础结构标准中定义。
虽然我无法在当前的ECMA规范中找到术语模块初始值设定项,但它在逻辑上从类型初始值设定项和全局的<Module>
特殊类(请参阅MethodDef的第22.26节,第40小节)。 这个特性是在.NET 1.1之后实现的(即从2.0开始)。 另请参阅此半官方说明。
这个问题不是关于C#,而是因为它是.NET的通用语言:C#不知道全局方法,并且不能创建<Module>
,更不用说它的cctor。 但是,Einar Egilsson已经认识到这种明显的缺陷,并创建了InjectModuleInitializer.exe,它允许您将其作为Visual Studio的后/编译步骤执行。 在C ++ .NET中,使用这种方法是微不足道的,推荐的做法是取代DllMain
。 请参阅本Voigt的SO回答(不是接受的答案),以及yoyoyoyosef的回答。
简而言之,模块初始化器是在加载模块之后调用的第一个方法(不一定在加载程序集时!),并且在调用任何类或实例方法之前。 它不接受参数,不返回任何值,但可以在其正文中包含任何托管代码。
事实上, 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();
}
}
}
编辑 :也考虑使用工厂模式,这将帮助您在返回创建的对象之前做所有需要的初始化。
这是通过设计:它最大限度地减少了静态构造函数之间的耦合。 你知道你的cctor将在你的类的任何东西初始化之前被调用,并且在你的类使用的任何类的cctors之后。 但是,与同一应用程序中无关的类相比,它的运行时间并不能保证。
如果您想确保您的代码在入口点之前运行,请考虑为主应用程序编写一个包装器。 一个简单的方法是将它放在一个单独的可执行文件中。
一个更独立的方式来做到这一点可能是:
上一篇: What is the earliest entrypoint that the CLR calls before calling any method in an assembly?