如何在动态加载的DLL中正确实现(c ++)线程本地存储?

时间:2012-03-03 19:38:41

标签: c++ windows multithreading dll thread-local-storage

在这种情况下,我的动态加载的dll由Windows资源管理器加载,以便将新的属性表(新选项卡)添加到文件/文件夹属性页面。

一个简单的例子是StrmExt.dll(download source)。在此示例(Microsoft提供的源代码)中,dll不使用线程本地存储(TLS),因此在同时加载多个属性页时会导致严重问题。

在查看源代码时,dll需要一个基于线程的变量(文件的文件路径)......

static TCHAR g_szFile[MAX_PATH];

将这一行代码更改为:

_declspec (thread) TCHAR g_szFile[MAX_PATH];

...启用dll以支持多个线程,因此支持属性表的多个实例。但是,我知道只有Windows Vista和更新版本支持此更改(Windows 7上的测试非常积极)。例如,XP不支持动态加载的库......它是known to crash the application。 (见最后一段)。

为了在XP上运行,我无法使用此声明。我怀疑我需要增强他们的DLL入口点:

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_STRMEXTLib);
        DisableThreadLibraryCalls(hInstance);
    }
    else if (dwReason == DLL_PROCESS_DETACH)
        _Module.Term();
    return TRUE;    // ok
}

...像这样......就像之前看到的here

struct ThreadData {
    static TCHAR g_szFile[MAX_PATH];
};
...
DWORD g_dwThreadIndex;

extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, 
                      DWORD dwReason, LPVOID /*pReserved*/)
{
    ThreadData* pData;   
    switch (dwReason) {
        case DLL_PROCESS_ATTACH:

            g_dwThreadIndex = ::TlsAlloc();
            if (g_dwThreadIndex == TLS_OUT_OF_INDEXES)
                return FALSE;

           // execute the DLL_THREAD_ATTACH code

        case DLL_THREAD_ATTACH:

            // allocate memory for this thread
            pData = (ThreadData*) ::LocalAlloc(LPTR, sizeof(ThreadData));
            if (pData == 0)
                return FALSE;

            ::TlsSetValue(g_dwThreadIndex, (LPVOID) pData);
            break;

        case DLL_THREAD_DETACH:

            // release memory for this thread
            pData = (ThreadData*) ::TlsGetValue(g_dwThreadIndex);
            if (pData != 0)
                ::LocalFree((HLOCAL) pData);
            break;

        case DLL_PROCESS_DETACH:

            // release memory for this thread
            pData = (ThreadData*) ::TlsGetValue(g_dwThreadIndex);
            if (pData != 0)
                ::LocalFree((HLOCAL) pData);
            // release the TLS index
            ::TlsFree(g_dwThreadIndex);
            break;
    } 
    return TRUE;
}

无论我创建1个还是2个线程,这在第一次加载dll时都能正常工作。释放dll之后,Explorer会在下一次加载库时崩溃。

我误解了什么?我注意到原始开发人员故意禁用了dll进程附加通知的线程通知。为什么?

DisableThreadLibraryCalls(hInstance);

先谢谢。

1 个答案:

答案 0 :(得分:1)

在这种情况下,最好避免出现问题。是的,你可能拥有比进程更多的线程,是的,每个属性表只与一个线程相关联,但反过来却无法保证。两个属性表可以共享一个线程,这取决于操作系统。 (并且这些未记录的决定在版本之间发生变化)。

相反,请使用lParam的{​​{1}}成员。它足够大,可以在64位系统上保持指针。指出你自己的班级。终身管理比你尝试的DLL附加/分离简单得多; Windows会在适当的时候调用您的PropSheetPageProc