4

在过去的几年里,我偶尔会想知道DLL_PROCESS_ATTACH在 .NET 世界中有什么等价的(臭名昭著的)。我所说的任何文档都稍微简化了一点,即类的最早入口点是静态构造函数(cctor),但是您无法影响调用它的时间也不能定义一个保证在任何其他 cctor 之前调用的 cctor或字段初始化程序,hack,如果从未使用过该类,它甚至可能根本不会被调用。

所以,如果你想保证在你的程序集的任何方法被调用之前已经初始化了一些东西,并且你不想在你的程序集中的每个类中添加一个 cctor,你可以采取什么方法?或者,我多年来一直错过的 .NET 中是否有一个简单的托管解决方案?

4

3 回答 3

4

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

经过一番研究,我偶然看到了Microsoft 的这篇文章,它解释了在内部混合托管和非托管代码的问题DllMain以及解决方案,该解决方案来自 CLI 的第二个版本,模块初始化程序。引用:

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

虽然我无法在当前的 ECMA 规范中找到术语模块初始化器,但它在逻辑上遵循类型初始化器和全局<Module>特殊类(参见第 22.26 节关于 MethodDef 的第 40 小点)。此功能是.NET 1.1 之后实现的(即从 2.0 开始)。另请参阅此半官方描述

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

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

于 2010-07-30T10:20:49.307 回答
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();
        }
    }
}

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

于 2010-07-29T14:54:27.790 回答
1

这是设计使然:它最大限度地减少了静态构造函数之间的耦合。您知道您的 cctor 将在您的类中的任何内容初始化之前以及在您的类使用的任何类的 cctors 之后被调用。但是,与同一应用程序中不相关的类相比,它何时运行并不能保证。

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

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

  1. 以正确的顺序运行所需的任何启动代码。不要在程序集中引用不应初始化的任何类型。
  2. 创建您自己的应用程序域
  3. 在第二个应用程序域中运行真正的入口点
于 2010-07-29T14:51:49.153 回答