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;
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;
}
{
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)();
}
{
FNSLEEP pfnSleep = (FNSLEEP)::GetProcAddress(g_hDLLModule, “DoSleep”);
ATLASSERT(pfnSleep);
(*pfnSleep)();
}
DWORD WINAPI ThreadProc( LPVOID lpParam)
{
CallTestDLL();
return 0;
}
{
CallTestDLL();
return 0;
}
g_hDLLModule = ::LoadLibrary(_T(“TestDLL.dll”));
ATLTRACE(“[Thread %d] LoadLibrary=0x%.8x
“, ::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);
ATLTRACE(“[Thread %d] LoadLibrary=0x%.8x
“, ::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 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();
{
CallTestDLL();
DoSomethingElse(); // 延遲執行緒結束
return 0;
}
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 //主執行緒
[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 不會被呼叫。