如何正确等待DllMain中创建的线程执行完成并解决输出异常
问题根因
- DllMain执行期间系统会持有全局的加载器锁(Loader Lock),原逻辑把等待线程的代码放在
DLL_PROCESS_DETACH分支,这个分支触发时进程已经进入退出流程:CRT库已经开始执行清理,printf依赖的标准输出缓冲、CRT内部状态可能已经被销毁,没来得及刷新的输出会直接丢失;同时系统会开始强制终止非主线程,工作线程根本拿不到足够时间跑完0-99的计数循环。 - 调整
printf和OutputDebugStringW顺序会得到不同结果,是因为两个API底层依赖完全不同:OutputDebugStringW走系统调试输出通道,不依赖CRT,也不会争抢加载器锁;printf是CRT提供的函数,依赖CRT内部锁和输出缓冲,进程退出阶段CRT清理时机的微小差异(比如两个API调用顺序不同带来的执行时间差),都会直接改变最终能打印出来的计数长度。 - 原逻辑还隐含死锁风险:如果在持有加载器锁的状态下等待工作线程,工作线程启动时需要获取加载器锁处理
DLL_THREAD_ATTACH通知,会直接和主线程形成死锁。
仅修改DllMain的修复方案
核心思路是把工作线程的执行、等待逻辑全部移到DLL_PROCESS_ATTACH分支,在DLL加载阶段、进程启动未退出时就跑完所有工作线程逻辑,避免在退出阶段等待线程触发各类未定义行为,具体修改点:
- 进入
DLL_PROCESS_ATTACH分支后首先调用DisableThreadLibraryCalls(hModule),关闭当前DLL的线程attach/detach通知,消除工作线程启动时争抢加载器锁导致的死锁风险。 - 创建完工作线程后立刻调用
WaitForSingleObject等待线程执行完成,不要把等待逻辑放到DLL_PROCESS_DETACH分支。 - 等待结束后调用
CloseHandle释放线程句柄,避免资源泄漏。 - 所有
printf调用后增加fflush(stdout)强制刷新输出缓冲,避免内容留在缓冲区没输出到控制台。
修正后的完整DllMain代码如下:
#include <Windows.h> #include <iostream> DWORD WINAPI function(LPVOID lpParam) { for (int i = 0; i < 100; i++) { printf("%d \n", i); fflush(stdout); // 强制刷新输出缓冲,避免内容丢失 } return 0; } HANDLE funcThread; BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: // 关闭当前DLL的线程通知,避免加载器锁死锁 DisableThreadLibraryCalls(hModule); funcThread = CreateThread( NULL, // 默认安全属性 0, // 默认栈大小 function, // 线程函数 NULL, // 线程参数 0, // 默认创建标志 NULL); // DLL加载阶段直接等待线程执行完成,此时CRT状态完整,不会被提前清理 WaitForSingleObject(funcThread, INFINITE); CloseHandle(funcThread); // 释放线程句柄 OutputDebugStringW(L"attach process!\n"); printf("attach process!\n"); fflush(stdout); break; case DLL_PROCESS_DETACH: // 工作线程已经在ATTACH阶段执行完成,无需重复等待 OutputDebugStringW(L"detach process!\n"); printf("detach process!\n"); fflush(stdout); break; case DLL_THREAD_ATTACH: // 调用DisableThreadLibraryCalls后这两个分支不会被触发,保留无影响 printf("attach thread!\n"); fflush(stdout); OutputDebugStringW(L"attach thread!\n"); break; case DLL_THREAD_DETACH: OutputDebugStringW(L"detach thread!\n"); printf("detach thread!\n"); fflush(stdout); break; } return TRUE; }
注意事项
不要在DLL_PROCESS_DETACH中执行线程等待、复杂逻辑调用,这个执行点属于进程退出的最后阶段,绝大多数系统API、CRT API的调用行为都是未定义的,微软官方文档也明确要求DllMain中只允许执行极简的初始化/清理操作,禁止做线程等待、复杂库调用等重逻辑。
内容的提问来源于stack exchange,提问作者GuangJun Liu
相关产品推荐
相关产品推荐

