[Windows編程] 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》 上有這樣的示例代碼。

 

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 不會被調用。

 

 

 

 

發佈了160 篇原創文章 · 獲贊 8 · 訪問量 90萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章