- 主题:DLL一个众所不知的坑
我今天读微软的DLL的官方文档的时候,看到了这样一句话:
"When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap memory only if the DLL is being unloaded dynamically (the lpvReserved parameter is NULL). "
特别强调:only if!!!
该页面同时还有这样一句话:
"If your DLL is linked with the C run-time library (CRT), the entry point provided by the CRT calls the constructors and destructors for global and static C++ objects. Therefore, these restrictions for DllMain also apply to constructors and destructors and any code that is called from them."
举个例子解释一下:假如你有一个unique_ptr是一个global variable.那么,你需要在Dllmain里面视情况把这个unique_ptr给release掉,以防止它的析构函数被调用。不然在某些情况下会crash.
--
修改:snnn FROM 112.85.242.*
FROM 115.231.225.*
手动卸载时 可以 运行自己的清理工作或者 析构函数? 进程退出系统自动卸载DLL时,不要调用清理工作的函数或析构函数?
--
FROM 14.154.1.*
MS要码农关注这个问题,几乎不太现实
全局对象或者单例对象里随便定义一个std::string类型的成员,这个对象在析构时就会自动调用std::string的析构来释放内存。
不可能在DllMain()里写代码提前把每个std::string给先释放掉。
不过process main heap的锁在执行DLL_PROCESS_DETACH时会先拿住,所以process main heap的状态不会是不确定的。如果std::string是释放到process main heap里,就没问题。
稍微好一点的处理,是退进程时,先把所有的线程都停掉(包括码农自己开的线程,和OS API内部开的线程),只剩下主线程。
之后再执行ExitProcess()之类的操作,或者从exe的main()执行到CRT内部的退出代码。这些操作都在主线程里执行,也不会去杀线程(这是导致某些锁或者数据结构处于未确定状态的根因:线程执行到任意位置被杀),没有问题。
也就是说,需要从exe全局考虑,而不是靠单个dll自己来处理这个问题。
另外,除了ExitProcess()内部会杀线程,也要避免自己调用TerminateThread()去杀线程。
--
修改:z16166 FROM 114.254.115.*
FROM 114.254.115.*
【 在 Algoquant 的大作中提到: 】
: 手动卸载时 可以 运行自己的清理工作或者 析构函数? 进程退出系统自动卸载DLL时,不要调用清理工作的函数或析构函数?
是这个意思。因为假如你用LoadLibrary函数加载了一个DLL但是没有手动卸载,那么那个DLL会晚于主exe被摧毁掉。通常来说那已经太晚了,很多函数已经不工作了。
--
FROM 112.85.242.*
> 稍微好一点的处理,是退进程时,先把所有的线程都停掉(包括码农自己开的线程,和OS API内部开的线程),只剩下主线程。
我说的不是线程的问题。是DLL的退出顺序的问题。
--
FROM 112.85.242.*
这个问题的本质就是杀线程导致的线程间共享的数据出问题(数据状态不一致,所谓的inconsistent,不限于堆内存)
所以要根本解决这个问题,就是任何时候都不要用TerminateThread()杀线程,也不要把线程留给ExitProcess()的OS内部实现去杀。
而是要保证在进入ExitProcess()之前时,所有非主线程的那些线程都正常退出了。
MS的这个文档刻意提一下dll在进程退出时不要释放内存,就是为了避免内存(heap)被那些被强杀的线程给破坏了状态后再去操作这些内存,从而出现各种非预期的结果。
MS的Raymond Chen 2015年在他的Old New Thing系列blog里提过这个问题:
devblogs dot microsoft dot com /oldnewthing/20120105-00/?p=8683
【 在 snnn 的大作中提到: 】
: > 稍微好一点的处理,是退进程时,先把所有的线程都停掉(包括码农自己开的线程,和OS API内部开的线程),只剩下主线程。
: 我说的不是线程的问题。是DLL的退出顺序的问题。
--
FROM 114.254.115.*
StackOverlow 6年前也有人讨论过这个问题,有人回答得很清楚
stackoverflow dot com /questions/53818859/a-dll-should-free-heap-memory-only-if-the-dll-is-unloaded-dynamically
那就是即使你不用TerminateThread()显式去杀线程,ExitProcess()这个OS API或者系统调用的内部实现也会去把当前线程之外的线程全都给强杀了
线程被强杀时,它可能处于任意代码的执行位置,比如对于多个线程间共享的数据,它只处理了一半,这就会导致数据不一致,或者说不能满足预期的invariant。然后其他线程再去尝试访问这些残破的数据,不知道会发生什么。那么最直接的规避,就要避免去访问残破的数据。
但是最好的规避,是任何时候自己不主动杀线程,也不让ExitProcess()去杀线程。
--
FROM 114.254.115.*