如何在动态加载的 DLL 中正确实现(C++)线程本地存储?
Posted
技术标签:
【中文标题】如何在动态加载的 DLL 中正确实现(C++)线程本地存储?【英文标题】:How do you properly implement (C++) thread local storage in a dynamically loaded DLL? 【发布时间】:2012-03-03 19:38:41 【问题描述】:在这种情况下,我的动态加载的 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 后,资源管理器在下次加载库时崩溃。
我误会了什么?我注意到原始开发人员在 DLL 进程附加通知时故意禁用线程通知。为什么?
DisableThreadLibraryCalls(hInstance);
【问题讨论】:
DisableThreadLibraryCalls 只是一种优化。 “Explorer 崩溃”不是恰当的问题描述,您必须更好地记录崩溃详细信息。 公平的cmets。我知道它是一个优化选项,但很好奇它是否存在其他原因。我拥有的 XP 系统目前位于另一个系统上的 VM 中,我将在另一天看到有关提供异常报告的信息。但是,我质疑示例中的通知代码。它看起来是否正确,尤其是在 Windows 资源管理器的上下文中?即进程附加逻辑在它如何执行线程附加逻辑方面是否正确?而且,TLS 是否在线程分离和进程分离时正确释放? 显然您必须删除对DisableThreadLibraryCalls
的调用才能接收DLL_THREAD_ATTACH
和DLL_THREAD_DETACH
。除此之外,我猜由于 DllMain 的变幻莫测,您可能会有一些泄漏。
“我遇到了一个全局变量问题,所以我使用了 TLS,现在我遇到了两个问题。”丢掉全局变量就没有问题了。
我认为您甚至不能保证每个属性表都有一个线程。丢失 TLS;将数据存储在PROPSHEETPAG::lParam
中。它的存在是有原因的。
【参考方案1】:
在这种情况下,最好完全避免该问题。是的,您可能拥有比进程更多的线程,是的,每个属性表将仅与一个线程相关联,但不能保证相反。两个属性表可以共享一个线程,这取决于操作系统。 (并且这些未记录的决定会在版本之间发生变化)。
改为使用PROPSHEETPAGE
的lParam
成员。它足够容纳一个指针,在 64 位系统上也是如此。把它指向你自己的班级。生命周期管理比您尝试的 DLL 附加/分离要简单得多; Windows 在适当的时候调用您的PropSheetPageProc
。
【讨论】:
感谢您正确指导我。我现在有一个 32 位和一个 64 位工作 DLL,似乎可以在所有 Windows O/S 版本、XP 及更高版本上工作。以上是关于如何在动态加载的 DLL 中正确实现(C++)线程本地存储?的主要内容,如果未能解决你的问题,请参考以下文章