好的。首先,让我们涵盖一些小问题:
正如David在评论中提到的,您不需要使用_beginthreadex()而不是CreateThread()。同样,在任何当前支持的Visual Studio和Windows版本上,使用ExitThread()或类似函数代替_endthreadex()也是可以的。
尽管MSDN文章中所说的事情,被广泛接受的智慧是永远不要使用TerminateThread()。
通常认为,只要您理解加载器锁定所暗示的限制,就可以在DllMain的DLL_PROCESS_ATTACH处理中使用CreateThread(),但如果您能够使用正确的初始化例程而不是DllMain,那么就更好了。
因此,如果我正确理解您的情况,它可以总结如下:
您的DLL需要一个或多个后台线程。
可执行文件在没有警告的情况下卸载了您的DLL。
这有点愚蠢,但这不是您的错。幸运的是,这并不是不可能解决的。
如果线程在可执行文件认为已卸载您的DLL之后继续运行是可以接受的,那么您可以使用FreeLibraryAndExitThread()模式。在初始化函数中和任何其他创建线程的地方,调用GetModuleHandleEx()来增加DLL引用计数。这样,当可执行文件调用FreeLibrary()时,如果任何线程仍在运行,则不会卸载DLL。线程通过调用FreeLibraryAndExitThread()退出,保持引用计数。
然而,这种方法可能无法直接满足您的需求,因为它不允许您检测可执行文件何时卸载库,以便您可以通知线程终止。
可能有更聪明的解决方案,但我建议使用一个帮助DLL。这个想法是,辅助 DLL 而不是你的主 DLL 跟踪线程引用计数,即每当创建后台线程时加载辅助 DLL,每当后台线程退出时卸载它。辅助 DLL 只需要包含一个调用 SetEvent() 然后 FreeLibraryAndExitThread() 的单个函数。
当后台线程收到 DLL 正在被卸载的通知时,它会进行清理,然后调用辅助 DLL 来设置事件并退出线程。一旦事件被设置,你的主 DLL 分离例程就知道该线程不再运行来自主 DLL 的代码。一旦每个后台线程完成了清理,主 DLL 卸载是安全的——它不重要线程仍在运行,因为它们正在运行来自辅助 DLL 而不是主 DLL 的代码。辅助 DLL 反过来将在最后一个线程调用 FreeLibraryAndExitThread() 时自动卸载。
再看一年或者一段时间之后,可能把这个反转过来会更安全:让主 DLL 只包含初始化函数和程序调用的其他函数,以及一个 DllMain,该函数发出信号要求后台线程退出,并有一个包含所有其他内容的次要 DLL。
特别是,如果次要 DLL 包含所有后台线程需要的代码,则在后台线程仍在运行时卸载主 DLL 是安全的。
这种变体的优点是,当后台线程看到退出信号时,如果主 DLL 已经卸载,那么也没关系,因此你的 DllMain 函数不必等待并保持加载器锁定。这样,如果其中一个后台线程无意中执行需要加载器锁定的操作,进程就不会出现死锁。
作为相同想法的变体,如果你真的不想在 CRT 线程上使用 FreeLibraryAndExitThread(),可以在次要 DLL 中有一个额外的线程来协调卸载。这个线程将使用 CreateThread() 启动,并且不使用任何 CRT 函数,因此通过 FreeLibraryAndExitThread() 退出对它来说是绝对安全的。它的唯一责任是在卸载次要 DLL 之前等待所有其他线程退出。
不再必须区分 CRT 和非 CRT 线程,但如果你想严格遵循文档中的规则,这是一种方法。