假设我在C ++中编写了一个DLL,并使用非平凡的析构函数声明了一个类的全局对象。卸载DLL时是否会调用析构函数?
答案 0 :(得分:37)
在Windows C ++ DLL中,所有全局对象(包括类的静态成员)将在使用DLL_PROCESS_ATTACH调用DllMain之前构造,并且在使用DLL_PROCESS_DETACH调用DllMain之后它们将被销毁。
现在,你必须考虑三个问题:
0 - 当然,全局非const对象是邪恶的(但你已经知道了,所以我会避免提到多线程,锁,神对象等)。
1 - 不保证对象或不同编译单元(即CPP文件)的构造顺序,因此如果两个对象在两个不同的CPP中实例化,则无法希望在B之前构造对象A.如果B依赖于A,这很重要。解决方案是将所有全局对象移动到同一个CPP文件中,因为在同一个编译单元内,对象的实例化顺序将是构造的顺序(以及顺序的反转)破坏)
2 - 在DllMain中有些事情是被禁止的。在构造函数中,这些东西也可能被禁止。所以避免锁定某些东西请参阅Raymond Chen关于此主题的优秀博客:
http://blogs.msdn.com/oldnewthing/archive/2004/01/27/63401.aspx
http://blogs.msdn.com/oldnewthing/archive/2004/01/28/63880.aspx
在这种情况下,延迟初始化可能很有趣:类仍处于“未初始化”状态(内部指针为NULL,布尔值为假,无论如何),直到您调用其中一个方法,此时它们将会初始化自己。如果你在main(或者一个main的后代函数)中使用那些对象,你就可以了,因为它们将在执行DllMain后被调用。
3 - 当然,如果DLL A中的某些全局对象依赖于DLL B中的全局对象,则应该非常小心DLL加载顺序,从而确保依赖性。在这种情况下,具有直接或间接循环依赖性的DLL将导致您疯狂的头痛。最好的解决方案是打破循环依赖。
P.S。:请注意,在C ++中,构造函数可以抛出,并且您不希望在DLL加载过程中出现异常,因此请确保您的全局对象在没有非常好的理由的情况下不会使用异常。由于正确编写的析构函数无权抛出,因此在这种情况下DLL卸载应该没问题。
答案 1 :(得分:6)
Microsoft的这个页面详细介绍了DLL初始化和全局变形的破坏:
http://msdn.microsoft.com/en-us/library/988ye33t.aspx
答案 2 :(得分:4)
如果您想查看链接.dll时执行的实际代码,请查看%ProgramFiles%\Visual Studio 8\vc\crt\src\dllcrt0.c
。
从检查开始,当dll CRT维护的内部引用计数达到零时,将通过_cexit()
调用析构函数。
答案 3 :(得分:3)
应该在应用程序结束或卸载DLL时调用它,以先到者为准。请注意,这在某种程度上取决于您正在编译的实际运行时。
此外,请注意非平凡的析构函数,因为存在时序和排序问题。您的DLL可以在 之后卸载您的析构函数所依赖的DLL,这显然会导致问题。
答案 4 :(得分:1)
当调用带有fdwReason = DLL_PROCESS_DETACH参数的DllMain时,表示应用程序卸载了DLL。这是调用全局/静态对象的析构函数之前的时间。
答案 5 :(得分:1)
在带有扩展名* .exe的Windows二进制图像文件中,* .dll位于PE format 这样的文件有入口点。您可以使用像
这样的dumpbin工具查看它dumpbin / headers dllname.dll
如果您使用Microsoft的C运行时,那么您的入口点将是类似的 * CRTStartup或* DllMainCRTStartup
此类函数执行c和c ++运行时的初始化,并分别将执行委托给(main,WinMain)或DllMain。
如果您使用Microsofts VC编译器,那么您可以在您的VC目录中查看此功能的源代码:
DllMainCRTStartup进程所有事情都需要在正常情况下从.data部分初始化/取消全局变量,当它在dll卸载期间检索通知DLL_PROCESS_DETACH时。例如: