ProcessExit - DLL卸载和静态变量的顺序

3

有一个EXE文件,它隐式加载了一些DLL文件,同时也显式地(使用LoadLibrary)加载了其他一些DLL文件。这个EXE正在执行ExitProcess(进程中还剩下1个线程),并且在忙于卸载一个名为A.DLL的DLL时。

不幸的是,A.DLL具有静态内容。atexit回调被调用,这个静态的析构函数开始被调用,留下一连串的析构函数,直到某个析构函数决定进行一些清理工作并加载一个DLL来完成此操作。这个DLL执行一些方法,直到由于内存访问违规而崩溃,因为它即将使用的一个静态变量在此DLL中已不存在。查看这个静态变量的堆栈跟踪,发现它的析构函数已经作为DLL卸载的一部分被调用。

发生了什么?DLL被加载,执行了一些方法,然后要使用同一DLL中的一个静态变量,但是这个静态变量已经被销毁了(静态变量只有在DLL卸载时才会被销毁)。所以它处于一种半悬停状态,既在执行方法,又在被销毁。

这个EXE似乎处于__tmainCRTStartup上下文中,这意味着用户创建的main函数已经返回了?DLL在用户主函数或tmainCRTStartup上下文中卸载吗?


tmainCRTStartup 中,DLL 在 .exe 的全局析构函数完成后被关闭。 - avakar
调用FreeLibrary()的代码可能会非常麻烦,关闭正在运行的程序是一项危险的任务。如果您无法从代码所有者那里获得帮助,则可以调用exit(0)。请注意C++11 std::quick_exit()函数,它解决了同类问题。 - Hans Passant
没有FreeLibrary调用,运行时正在卸载dll文件... - Science_Fiction
如果进程在__tmainCRTStartup的上下文中,那么我认为进程尚未开始卸载DLL(这会在__tmainCRTStartup将控制返回给Windows之后发生)。听起来问题与卸载DLL无关,而是与运行时库处理atexit回调有关(正如ElektroKraut所解释的那样)。如果是这样的话,使用ExitProcess而不是退出主函数应该可以解决问题 - 前提是您实际上不需要调用任何析构函数。当然,这取决于它们在做什么。 - Harry Johnston
1个回答

5
简单来说:静态对象的析构函数按照创建顺序的相反顺序调用,在内部通过注册atexit回调函数实现。唯一不同的情况是如果您手动卸载(FreeLibrary)一个DLL。
您描述的问题只是显示您存在循环依赖关系,这在静态构造函数/析构函数中很容易发生。您应该小心在析构函数中做什么,特别是在此时加载DLL似乎对我来说非常危险。

我同意你的看法,不过这是遗留代码。我也了解销毁顺序以及 atexit 回调函数。让我困惑的是,我们正在执行某些 dll 代码的中间过程中,但它的静态数据已被销毁。 - Science_Fiction
就像我之前所说的,静态对象相互依赖时这种情况总是可能发生的,特别是因为静态构造函数的顺序是未定义的(这也适用于同一DLL中的静态对象)... - ElektroKraut
A.DLL正在执行代码,它开始使用其静态库(在A.DLL内部)。这被销毁了。因此,同一个DLL在其静态库已被销毁的情况下正在运行代码。这让我感到困惑。 - Science_Fiction
这是我想象中的发生方式(简化版):(1) 执行静态构造函数 A (2) 加载 DLL (3) 执行 DLL 的静态构造函数 B (4) 调用 exit (5) 调用 DLL 的静态析构函数 ~B (6) 调用静态构造函数 ~A,但尝试使用 B -> 崩溃! - ElektroKraut
您存在一个循环依赖,从而导致重入。在清理 A 的静态内容时,您执行了调用 A 的代码,假设其静态内容仍然有效。这个假设是无效的(因为 A 的静态内容正在被销毁),因此导致了您的问题。 - Raymond Chen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接