DLL_THREAD_DETACH 认识误区

DLL 里面使用TLS (Local Thread Storage ) 的常见做法是:在DLLMain的DLL_PROCESS_ATTACH/DLL_THREAD_ATTACH 被调用的时候为每个线程(Thread)分配内存,而在DLL_THREAD_DETACH/DLL_PROCESS_DETACH 被调用的时候释放内存。 MSDN文章《Using Thread Local Storage in a Dynamic-Link Library》 上有这样的示例代码。

 

  1. BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle  
  2.     DWORD fdwReason,                    // reason called  
  3.     LPVOID lpvReserved)                 // reserved  
  4. {  
  5.     LPVOID lpvData;  
  6.     BOOL fIgnore;  
  7.     switch (fdwReason)  
  8.     {   
  9.         case DLL_PROCESS_ATTACH:   
  10.             // Allocate a TLS index.  
  11.             if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)                return FALSE;   
  12.          case DLL_THREAD_ATTACH:   
  13.              lpvData = (LPVOID) LocalAlloc(LPTR, 256);  //为每个Thread分配内存  
  14.             if (lpvData != NULL)  
  15.                 fIgnore = TlsSetValue(dwTlsIndex, lpvData);   
  16.             break;   
  17.          case DLL_THREAD_DETACH:   
  18.              lpvData = TlsGetValue(dwTlsIndex);  
  19.             if (lpvData != NULL)  
  20.                 LocalFree((HLOCAL) lpvData);  //释放内存  
  21.             break;   
  22.          case DLL_PROCESS_DETACH:   
  23.             lpvData = TlsGetValue(dwTlsIndex);  
  24.             if (lpvData != NULL)  
  25.                 LocalFree((HLOCAL) lpvData);  //释放内存  
  26.             TlsFree(dwTlsIndex);  
  27.             break;   
  28.          default:  
  29.             break;  
  30.     }   
  31.      return TRUE;  
  32. }  

 

这段代码认为DLL_THREAD_DETACH 总是会被调用, 但实际情况并非如此。在某些情况下DLL_THREAD_DETACH并不会被调用, 结果造成内存泄漏。 接下来做2个简单实验说明这个问题。

实验代码:

  1. typedef void (__stdcall *FNSLEEP)();  
  2. void CallTestDLL()  
  3. {  
  4.     FNSLEEP pfnSleep = (FNSLEEP)::GetProcAddress(g_hDLLModule, "DoSleep");  
  5.     ATLASSERT(pfnSleep);  
  6.     (*pfnSleep)();  
  7. }  
  8. DWORD WINAPI ThreadProc( LPVOID lpParam)  
  9. {  
  10.     CallTestDLL();  
  11.     return 0;  
  12. }    
  13. g_hDLLModule = ::LoadLibrary(_T("TestDLL.dll"));  
  14. ATLTRACE("[Thread %d] LoadLibrary=0x%.8x/n", ::GetCurrentThreadId());  
  15. CallTestDLL();  
  16. const int MAX_THREAD = 2;  
  17. HANDLE hThread[MAX_THREAD];  
  18. for (int i=0; i < MAX_THREAD; i++)  
  19. {  
  20.    hThread[i] = ::CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);    
  21. }  
  22. Sleep(MAX_THREAD * 1000);  
  23. ::FreeLibrary(g_hDLLModule);  

 

输出结果1:

  1. [Thread 4976] DLL_PROCESS_ATTACH                //主线程  
  2. [Thread 4976] LoadLibrary=0x0ecbf9d4  
  3. [Thread 4976] DoSleep() in DLL  
  4. [Thread 7860] DLL_THREAD_ATTACH                  //CreateThread 产生的线程  
  5. [Thread 736] DLL_THREAD_ATTACH                    //CreateThread 产生的线程  
  6. [Thread 736] DoSleep() in DLL  
  7. [Thread 7860] DoSleep() in DLL  
  8. [Thread 736] DLL_THREAD_DETACH  
  9. [Thread 7860] DLL_THREAD_DETACH  
  10. [Thread 4976] DLL_PROCESS_DETACH                //主线程  

 

以上输入结果我们看到每个Thread 调用DLL函数DoSleep 立即结束,这时候DLL_THREAD_DETACH 被正常调用。 这时只要候稍微改一下代码,会看到完全不同的结果。

  1. DWORD WINAPI ThreadProc( LPVOID lpParam)  
  2. {  
  3.     CallTestDLL();  
  4.     DoSomethingElse();  // 延迟线程结束  
  5.     return 0;  
  6. }   

 

输出结果2:

  1. [Thread 7448] DLL_PROCESS_ATTACH              //主线程  
  2. [Thread 7448] LoadLibrary=0x0b1cf9d4  
  3. [Thread 7448] DoSleep() in DLL  
  4. [Thread 6872] DLL_THREAD_ATTACH  
  5. [Thread 6556] DLL_THREAD_ATTACH  
  6. [Thread 6556] DoSleep() in DLL  
  7. [Thread 6872] DoSleep() in DLL  
  8. [Thread 7448] DLL_PROCESS_DETACH             //主线  

 

我们发现,CreateThread 产生的线程并没有调用DLL_THREAD_DETACH 。

结论 :如果是线程在DLL被卸载(调用FreeLibrary) 之前结束,则DLL_THREAD_DETACH 会被调用。 如果线程在DLL卸载之后结束,则DLL_THREAD_DETACH 不会被调用。

 

本文转自Hellocpp: http://www.hellocpp.net/Articles/Article/590.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值