[Windows編程] DLL_THREAD_DETACH 認識誤區

來源:互聯網
上載者:User

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

 

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.