轉(zhuǎn)自: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 被調(diào)用的時(shí)候?yàn)槊總€(gè)線程(Thread)分配內(nèi)存,而在DLL_THREAD_DETACH/DLL_PROCESS_DETACH 被調(diào)用的時(shí)候釋放內(nèi)存。 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); //為每個(gè)Thread分配內(nèi)存
??????????? if (lpvData != NULL)
??????????????? fIgnore = TlsSetValue(dwTlsIndex, lpvData);
??????????? break;
???????? case DLL_THREAD_DETACH:
???????????? lpvData = TlsGetValue(dwTlsIndex);
??????????? if (lpvData != NULL)
??????????????? LocalFree((HLOCAL) lpvData); //釋放內(nèi)存
??????????? break;
???????? case DLL_PROCESS_DETACH:
??????????? lpvData = TlsGetValue(dwTlsIndex);
??????????? if (lpvData != NULL)
??????????????? LocalFree((HLOCAL) lpvData); //釋放內(nèi)存
??????????? TlsFree(dwTlsIndex);
??????????? break;
???????? default:
??????????? break;
??? }
???? return TRUE;
}
這段代碼認(rèn)為DLL_THREAD_DETACH 總是會(huì)被調(diào)用, 但實(shí)際情況并非如此。在某些情況下DLL_THREAD_DETACH并不會(huì)被調(diào)用, 結(jié)果造成內(nèi)存泄漏。 接下來做2個(gè)簡單實(shí)驗(yàn)說明這個(gè)問題。
實(shí)驗(yàn)代碼:
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);
輸出結(jié)果1:
[Thread 4976] DLL_PROCESS_ATTACH??????????????? //主線程
[Thread 4976] LoadLibrary=0x0ecbf9d4
[Thread 4976] DoSleep() in DLL
[Thread 7860] DLL_THREAD_ATTACH????????????????? //CreateThread 產(chǎn)生的線程
[Thread 736] DLL_THREAD_ATTACH??????????????????? //CreateThread 產(chǎn)生的線程
[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??????????????? //主線程
以上輸入結(jié)果我們看到每個(gè)Thread 調(diào)用DLL函數(shù)DoSleep 立即結(jié)束,這時(shí)候DLL_THREAD_DETACH 被正常調(diào)用。 這時(shí)只要候稍微改一下代碼,會(huì)看到完全不同的結(jié)果。
DWORD WINAPI ThreadProc( LPVOID lpParam)
{
??? CallTestDLL();
??? DoSomethingElse(); // 延遲線程結(jié)束
??? return 0;
}??
輸出結(jié)果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???????????? //主線程
我們發(fā)現(xiàn),CreateThread 產(chǎn)生的線程并沒有調(diào)用DLL_THREAD_DETACH 。
結(jié)論:
如果是線程在DLL被卸載(調(diào)用FreeLibrary) 之前結(jié)束,則DLL_THREAD_DETACH 會(huì)被調(diào)用。 如果線程在DLL卸載之后結(jié)束,則DLL_THREAD_DETACH 不會(huì)被調(diào)用。