在调用程序集中的任何方法之前,CLR 调用的最早入口点是啥?
Posted
技术标签:
【中文标题】在调用程序集中的任何方法之前,CLR 调用的最早入口点是啥?【英文标题】:What is the earliest entrypoint that the CLR calls before calling any method in an assembly?在调用程序集中的任何方法之前,CLR 调用的最早入口点是什么? 【发布时间】:2011-03-22 18:30:10 【问题描述】:在过去的几年里,我偶尔会想知道在 .NET 世界中有什么等价的(臭名昭著的)DLL_PROCESS_ATTACH
。我所说的任何文档都稍微简化了一点,即类的最早入口点是静态构造函数 (cctor),但您不能影响 when it is called,也不能定义一个保证在任何其他 cctor 之前调用的 cctor 或字段初始化器,hack,如果从不使用该类,它甚至可能根本不会被调用。
所以,如果您想保证在调用程序集的任何方法之前 初始化某些内容,并且您不想为程序集中的每个类添加一个 cctor,那么您可以采用什么方法拿?还是有一个我多年来一直错过的简单、托管的 .NET 解决方案?
【问题讨论】:
为什么?你想做什么? @SLaks:为什么?在具有多个类的静态实用程序库中,您不希望每个类中的每个方法或 cctor 调用全局初始化程序,从而违反 DRY。此外,比较现有的DllMain
,它的存在是有目的的(并且有其缺点)。如果您想挂钩或绕行方法或想使用自己的AssemblyResolver 或执行其他与程序集加载相关的任务。一些常见的例子是 System.Data 模块和msvcm80.dll
【参考方案1】:
我通常不会回答我自己的问题,但同时我确实找到了一个以前没有出现过的答案,所以我开始了。
经过一番研究,我偶然发现了this post by Microsoft,它解释了在DllMain
中混合托管和非托管代码的问题以及解决方案,它出现在第二版 CLI,模块初始化器。引用:
这个初始化器在 本机 DllMain(换句话说, 在装载机锁之外)但在任何之前 托管代码正在运行或托管数据正在运行 从该模块访问。这 模块 .cctor 的语义是 与 .cctors 类非常相似 并在 ECMA C# 中定义 公共语言基础设施 标准。
虽然我无法在当前的 ECMA 规范中找到术语 module initializer,但它在逻辑上遵循 type initializer 和全局 <Module>
特殊类 (参见 MethodDef 的第 22.26 节,第 40 小点)。此功能是在之后 .NET 1.1(即从 2.0 开始)实现的。另见this semi-official description。
这个问题不是关于 C#,而是因为它是 .NET 的通用语言:C# 不知道全局方法,你不能创建 <Module>
,更不用说它的 cctor。但是,Einar Egilsson 拥有 recognized this apparent deficiency 并创建了 InjectModuleInitializer.exe,它允许您在 Visual Studio 的后/编译步骤中执行此操作。在 C++.NET 中,使用此方法是微不足道的,建议使用此方法代替 DllMain
。另见this SO answer by Ben Voigt(不是公认的答案)和这个SO answer by 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();
编辑:还可以考虑使用Factory pattern,这将帮助您在返回创建的对象之前完成所有需要的初始化。
【讨论】:
抱歉,我的笔记"(simplified)",在我的第一个版本中,被我不小心删除了。我知道静态字段是在静态 cctor 之前初始化的,并且它们的顺序没有定义。这只会为在所有这些之前调用方法提供更强有力的案例。注意:我编辑了我的 q。为了反映这种明显的模棱两可,感谢提及;-) 关于如何定义什么最早的入口点是什么? 最早的入口点是静态初始化器和构造器我不确定它是否是您需要的,但如果您需要执行类似 preinit 之类的操作,工厂模式可能会有所帮助。 虽然您的回答很有趣,但不幸的是,这不是我想要的。也许我的问题措辞有误,但请看看我添加的答案,这正是我想要的。【参考方案3】:这是设计使然:它最大限度地减少了静态构造函数之间的耦合。您知道您的 cctor 将在您的类中的任何内容初始化之前以及在您的类使用的任何类的 cctor 之后被调用。但与同一应用程序中不相关的类相比,无法保证它何时会运行。
如果您想确保您的某些代码在入口点之前运行,请考虑为主应用程序编写一个包装器。一种直接的方法是将其放入单独的可执行文件中。
一种更独立的方法可能是:
-
以正确的顺序运行所需的任何启动代码。不要在程序集中引用任何不应初始化的类型。
创建您自己的应用域
在第二个应用程序域中运行真正的入口点
【讨论】:
这可能是要走的路,我害怕这个答案突然出现。顺便说一句,当您的程序集本身不是应用程序或在托管环境(如 ASP.NET)中使用时,单独的可执行文件几乎没有用处。以上是关于在调用程序集中的任何方法之前,CLR 调用的最早入口点是啥?的主要内容,如果未能解决你的问题,请参考以下文章