在 DLL 中声明的全局变量会发生什么?

2021-12-25 00:00:00 windows dll c++

假设我用 C++ 编写了一个 DLL,并声明了一个具有非平凡析构函数的类的全局对象.DLL卸载时会不会调用析构函数?

Let's say I write a DLL in C++, and declare a global object of a class with a non-trivial destructor. Will the destructor be called when the DLL is unloaded?

推荐答案

在 Windows C++ DLL 中,所有全局对象(包括类的静态成员)将在使用 DLL_PROCESS_ATTACH 调用 DllMain 之前构造,并且它们将被在使用 DLL_PROCESS_DETACH 调用 DllMain 之后立即销毁.

In a Windows C++ DLL, all global objects (including static members of classes) will be constructed just before the calling of the DllMain with DLL_PROCESS_ATTACH, and they will be destroyed just after the call of the DllMain with DLL_PROCESS_DETACH.

现在,您必须考虑三个问题:

Now, you must consider three problems:

0 - 当然,全局非常量对象是邪恶的(但你已经知道了,所以我将避免提及多线程、锁、上帝对象等)

0 - Of course, global non-const objects are evil (but you already know that, so I'll avoid mentionning multithreading, locks, god-objects, etc.)

1 - 对象或不同编译单元(即 CPP 文件)的构造顺序无法保证,因此如果两个对象在两个不同的 CPP 中实例化,则不能希望对象 A 在 B 之前构造.如果 B 依赖于 A,这很重要. 解决方案是将所有全局对象移动到同一个 CPP 文件中,因为在同一个编译单元内,对象的实例化顺序将是构造顺序(以及顺序的倒数)毁灭)

1 - The order of construction of objects or different compilation units (i.e. CPP files) is not guaranteed, so you can't hope the object A will be constructed before B if the two objects are instanciated in two different CPPs. This is important if B depends on A. The solution is to move all global objects in the same CPP file, as inside the same compilation unit, the order of instanciation of the objects will be the order of construction (and the inverse of the order of destruction)

2 - 在 DllMain 中有一些禁止做的事情.这些事情可能在构造函数中也是被禁止的.所以避免锁定某些东西.请参阅 Raymond Chen 关于该主题的优秀博客:

2 - There are things that are forbidden to do in the DllMain. Those things are probably forbidden, too, in the constructors. So avoid locking something. See Raymond Chen's excellent blog on the subject:

  • 不在 DllMain 中做任何可怕的事情的一些理由
  • 不在 DllMain 中做任何可怕的事情的另一个原因:无意的死锁
  • 不在 DllMain 中做任何可怕的事情的一些理由,第 3 部分

在这种情况下,延迟初始化可能很有趣:类保持在未初始化"的状态.状态(内部指针为 NULL,布尔值为假,无论如何),直到您调用它们的方法之一,此时它们将初始化自己.如果你在 main(或 main 的后代函数之一)中使用这些对象,你会没事的,因为它们会在 DllMain 执行后被调用.

In this case, lazy initialization could be interesting: The classes remain in an "un-initialized" state (internal pointers are NULL, booleans are false, whatever) until you call one of their methods, at which point they'll initialize themselves. If you use those objects inside the main (or one of the main's descendant functions), you'll be ok because they will be called after execution of DllMain.

3 - 当然,如果 DLL A 中的某些全局对象依赖于 DLL B 中的全局对象,那么您应该非常小心 DLL 加载顺序,以及依赖关系.在这种情况下,具有直接或间接循环依赖关系的 DLL 会让您非常头疼.最好的解决方案是打破循环依赖.

3 - Of course, if some global objects in DLL A depend on global objects in DLL B, you should be very very careful about DLL loading order, and thus dependancies. In this case, DLLs with direct or indirect circular dependancies will cause you an insane amount of headaches. The best solution is to break the circular dependancies.

P.S.:请注意,在 C++ 中,构造函数可以抛出,并且您不希望在 DLL 加载过程中出现异常,因此请确保您的全局对象不会在没有非常非常好的理由的情况下使用异常.由于正确编写的析构函数无权抛出,因此在这种情况下卸载 DLL 应该没问题.

P.S.: Note that in C++, constructor can throw, and you don't want an exception in the middle of a DLL loading, so be sure your global objects won't be using exception without a very, very good reason. As correctly written destructors are not authorized to throw, the DLL unloading should be ok in this case.

相关文章