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

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/WinGeek/archive/2009/06/01/4230741.aspx

 

聞香止步 淘寶 拍拍 有啊 易趣店 收集
常年經營木雕系列產品:
海南黃花梨、越南黃花梨、草花梨、小葉紫檀、黑檀、香榧木、綠檀木、黃楊木 擺件;
紫檀、綠檀木、黃楊木、桃木、漆藝髮簪 
木梳 樟木壁掛 佛珠 車飾

收藏送禮佳品,也可聯繫訂做

淘寶店:http://shop36570193.taobao.com
拍拍店:http://421840135.paipai.com/

聯繫方式:
QQ   421840135
旺旺 xiaobaitucsl
郵件 [email protected]

朋友,有空來看看,喜歡的朋友請收藏

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章