轉自:CSDN 微軟 陳本峰 http://blog.csdn.net/WinGeek/
文章鏈接:http://blog.csdn.net/WinGeek/archive/2009/06/01/4230741.aspx
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》 上有這樣的示例代碼。
BOOL WINAPI DllMain(HINSTANCE hinstDLL, // DLL module handle
??? DWORD fdwReason,??????????????????? // reason called
??? LPVOID lpvReserved)???????????????? // reserved
{
??? LPVOID lpvData;
??? BOOL fIgnore;
??? switch (fdwReason)
??? {
??????? case DLL_PROCESS_ATTACH:
??????????? // Allocate a TLS index.
??????????? if ((dwTlsIndex = TlsAlloc()) == TLS_OUT_OF_INDEXES)??????????????? return FALSE;
???????? case DLL_THREAD_ATTACH:
???????????? lpvData = (LPVOID) LocalAlloc(LPTR, 256); //為每個Thread分配內存
??????????? if (lpvData != NULL)
??????????????? fIgnore = TlsSetValue(dwTlsIndex, lpvData);
??????????? break;
???????? case DLL_THREAD_DETACH:
???????????? lpvData = TlsGetValue(dwTlsIndex);
??????????? if (lpvData != NULL)
??????????????? LocalFree((HLOCAL) lpvData); //釋放內存
??????????? break;
???????? case DLL_PROCESS_DETACH:
??????????? lpvData = TlsGetValue(dwTlsIndex);
??????????? if (lpvData != NULL)
??????????????? LocalFree((HLOCAL) lpvData); //釋放內存
??????????? TlsFree(dwTlsIndex);
??????????? break;
???????? default:
??????????? break;
??? }
???? return TRUE;
}
這段代碼認為DLL_THREAD_DETACH 總是會被調用, 但實際情況并非如此。在某些情況下DLL_THREAD_DETACH并不會被調用, 結果造成內存泄漏。 接下來做2個簡單實驗說明這個問題。
實驗代碼:
typedef void (__stdcall *FNSLEEP)();
void CallTestDLL()
{
??? FNSLEEP pfnSleep = (FNSLEEP)::GetProcAddress(g_hDLLModule, "DoSleep");
??? ATLASSERT(pfnSleep);
??? (*pfnSleep)();
}
DWORD WINAPI ThreadProc( LPVOID lpParam)
{
??? CallTestDLL();
??? return 0;
}??
g_hDLLModule = ::LoadLibrary(_T("TestDLL.dll"));
ATLTRACE("[Thread %d] LoadLibrary=0x%.8x\n", ::GetCurrentThreadId());
CallTestDLL();
const int MAX_THREAD = 2;
HANDLE hThread[MAX_THREAD];
for (int i=0; i < MAX_THREAD; i++)
{
?? hThread[i] = ::CreateThread(NULL, 0, ThreadProc, 0, 0, NULL);
}
Sleep(MAX_THREAD * 1000);
::FreeLibrary(g_hDLLModule);
輸出結果1:
[Thread 4976] DLL_PROCESS_ATTACH??????????????? //主線程
[Thread 4976] LoadLibrary=0x0ecbf9d4
[Thread 4976] DoSleep() in DLL
[Thread 7860] DLL_THREAD_ATTACH????????????????? //CreateThread 產生的線程
[Thread 736] DLL_THREAD_ATTACH??????????????????? //CreateThread 產生的線程
[Thread 736] DoSleep() in DLL
[Thread 7860] DoSleep() in DLL
[Thread 736] DLL_THREAD_DETACH
[Thread 7860] DLL_THREAD_DETACH
[Thread 4976] DLL_PROCESS_DETACH??????????????? //主線程
以上輸入結果我們看到每個Thread 調用DLL函數DoSleep 立即結束,這時候DLL_THREAD_DETACH 被正常調用。 這時只要候稍微改一下代碼,會看到完全不同的結果。
DWORD WINAPI ThreadProc( LPVOID lpParam)
{
??? CallTestDLL();
??? DoSomethingElse(); // 延遲線程結束
??? return 0;
}??
輸出結果2:
[Thread 7448] DLL_PROCESS_ATTACH????????????? //主線程
[Thread 7448] LoadLibrary=0x0b1cf9d4
[Thread 7448] DoSleep() in DLL
[Thread 6872] DLL_THREAD_ATTACH
[Thread 6556] DLL_THREAD_ATTACH
[Thread 6556] DoSleep() in DLL
[Thread 6872] DoSleep() in DLL
[Thread 7448] DLL_PROCESS_DETACH???????????? //主線程
我們發現,CreateThread 產生的線程并沒有調用DLL_THREAD_DETACH 。
結論:
如果是線程在DLL被卸載(調用FreeLibrary) 之前結束,則DLL_THREAD_DETACH 會被調用。 如果線程在DLL卸載之后結束,則DLL_THREAD_DETACH 不會被調用。