在调用程序集中的任何方法之前,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 调用的最早入口点是啥?的主要内容,如果未能解决你的问题,请参考以下文章

确定一个方法是不是调用另一个包含新语句的程序集中的方法,反之亦然

SQL Server 中的 CLR 程序集 C#

在c#silverlight中调用c++ clr dll

CLR加载程序集代码时,JIT编译器对性能的产生的影响

C#基础之CLR的执行模型

如何从应用程序域中所有加载的程序集中获取所有静态类并使用反射调用静态方法