如何让windows平台多线程DLL完整退出

如果你在windows平台开发动态链接库,并且在链接库启动了内部线程,那么你很有可能发现加载你的DLL的程序在退出时会死锁,有时候虽然主程序界面没有了,但是打开任务管理器,发现进程还在。

虽然用户不觉得异常,但是最求完美的你,一定想让程序完整的退出,下面与你分享一下我这几天与这个问题奋战的经验总结。

最近做播放器插件开发,基于directshow、vlc、mplayer框架,各做了一个插件,三个插件中都使用了另外一个媒体DLL库(Mylib.dll),并且都是通过动态加载(LoadLibrary)使用的。该DLL比较复杂,内部使用的线程;另外directshow、vlc的插件自身也是一个DLL,mplayer不支持动态插件,是内置源码编译。

在没有针对进程退出做处理时,三个播放器(基于directshow的播放器测试了GraphEidt、Windows Media Player)都不能正常退出。

通过查看线程调用栈,是在Mylib.dll最后卸载时,dll的一个全局对象析构中,等待一个事件(Event)对象不返回,而这个事件应该是另一个线程退出前设置为有信号,但是整个进程除了主线程外,其他线程都已经结束了,这说明线程是被强行结束了,主线程调用栈显示已经进入了_ExitProcess中,应该是在此之前主线程杀死了所有子线程。

三个播放器框架都没有找到退出通知的机制,所以能够想到的办法只有使用atexit,在程序结束时调用一个Mylib.dll停止接口(Mylib_Stop)。都是没有效果,仍然不能正常退出。

但是与上面的情形不一样的是,mplayer还是挂死在Mylib.dll中的全局对象析构中;directshow与vlc却直接挂死在Mylib_Stop函数中,调用栈显示正在等待另一个线程退出,但是这个线程却在_ExitThread的地方卡住了。

要解决全局对象析构中的死锁问题,需要让线程正常退出,而不是被强行终止,因此需要在_ExitProcess前面卸载Mylib.dll。我们把这个任务增加到atexit的过程中,因为atexit的函数在退出主函数main后就会被调用,修改之后,mplayer真的可以正常退出了。

对于directshow与vlc,都是在一个插件DLL中启动Mylib.dll,我们发现atexit注册的函数,是在插件Dll卸载的时候被调用,调用栈中有DllMain函数。联想到执行DllMain函数的一些细节,觉得情况应该是这样:系统在调用DllMain时会有一个全局锁,主线程已经进入DllMain,所有锁已经被加上,另一个线程退出时也会调用DllMain,也需要这个锁,这样就造成了主线程等待另一个线程退出,而这个线程又在等待主线程占有的锁,形成了死锁情形。

另外在DLL中使用atexit注册的函数,不像我们期望的那样在进程退出时调用,而且在相应的DLL卸载时调用,这一点MSDN没有说明,但是通过跟踪到atexit里面,发现确实是根据DLL还是EXE分别处理的,同时也发现EXE中使用的是_imp___onexit函数。

尝试用_imp___onexit替换atexit,编译没有问题,但是运行会crash,因为注册的退出执行函数的代码实际是在插件DLL,而这个DLL在进程退出前已经卸载,代码页面失效。

最终我们只能修改Mylib.dll,去除全局变量析构中的等待事件死锁,并且不使用atexit,这样能够适应三个播放器;代价是Mylib.dll没有正常终止,里面的线程被强行终止,有可能会有一些善后工作无法完成。

 

总结如下:

1、带有内部线程DLL要想正常退出,需要导出一个退出函数接口,并且要求调用者在适当的时候调用。

2、要注意DLL中全局变量析构前,线程可能已经被强行终止,如果在析构中依赖某个线程完成一些工作,则要考虑这种可能性,但是直接等待线程句柄没有问题

3、在DLL代码中使用atexit注册的函数,不能期望在进程退出时被调用,另外不管是在DLL还是EXE代码中,不能将其他模块的函数注册到atexit中。

你可能感兴趣的:(如何让windows平台多线程DLL完整退出)