我有一个由许多DLL文件组成的游戏。其中一些DLL链接到相同的静态库(LIB)。
这样的事情:
Game.exe -> Root.dll -> Child.dll
| |
| '-> Common.lib (contains __declspec(thread))
|
'-> Common.lib (contains __declspec(thread))
Root.dll加载静态链接Common.lib的Child.dll。 Root还静态链接Common.lib。因为Common是静态链接的,所以它会直接编译到加载dll中(例如Root和Child)。
Common.lib包含使用线程本地存储(TLS)的变量。
__declspec(thread) static void* s_threadValues[PlatformThreadSlotsMax];
这会导致一些有问题的行为:Root.dll和Child.dll每个都包含一个不同的 TLS数据实例(s_threadValues)。即使在同一个线程上,如果Root.dll调用Common.lib中定义的函数,如果从Child.dll调用相同的函数,则s_threadValues的值将与其值不同。
由于两个DLL都是从同一个线程访问这个TLS,我希望共享TLS,但事实并非如此。
现在,如果我将Common.lib更改为动态链接(例如Common.dll),则此问题会再次出现不:对于Root.dll和Child.dll,s_threadValues是相同的。
这是预期的行为吗?无论如何在动态库之间使用它共享静态库中定义的TLS共享?
答案 0 :(得分:6)
这是完全正常的。每个DLL都有自己的库代码和数据副本。它自己的线程本地状态。
你可以通过假设它按你期望的方式工作来解释这个。然后两个DLL可能会意外地在不同的DLL之间共享自己的线程局部变量。显然这将是灾难性的。它无法以这种方式工作,因为没有机制可以跨模块共享TLS实例。槽索引有意保持模块专用,没有机制来获取__declspec(线程)变量的槽索引。显式调用TlsAlloc()并共享索引将是一种解决方法。不要去那里。
答案 1 :(得分:0)
虽然作者接受了Hans Passant的回答,但其中没有明显的解决方案。所以这就是我想出来的。不是很优雅,但将部分代码切割成Common.dll可能更糟/更难看。
class IContext
{
public:
static thread_local IContext* g_ctx;
virtual void setThreadContext() = 0;
virtual void print(int) = 0;
};
// Example
class Log
{
public:
// This code is static so will be compiled in each module. Thus
// gets access to different "g_ctx" per thread per module
// With TlsAlloc() approach we need another static variable for index,
// while thread_local will get it from _tls_index
// (see \Visual Studio\VC\crt\src\vcruntime\tlssup.cpp)
//
// mov r9d, dword ptr [_tls_index (07FEE05E1D50h)]
// mov rax, qword ptr gs:[58h]
// mov rsi, qword ptr [rax+r9*8] // g_ctx address is now in rsi
static void print(int x)
{
IContext::g_ctx->print(x);
}
};
#include "Common.h"
thread_local IContext* IContext::g_ctx = nullptr;
DLLEXPORT void setDllThreadContext(IContext* ctx)
{
// sets g_ctx in this module for current thread
IContext::g_ctx = ctx;
}
DLLEXPORT void runTask(IContext* ctx)
{
createThread(&someThreadFunc, ctx);
}
void someThreadFunc(IContext* ctx)
{
// will call setDllThreadContext() above
ctx->setThreadContext();
...
}
#include "Common.h"
thread_local IContext* IContext::g_ctx = nullptr;
// pointers to setDllThreadContext from each loaded DLL (engine plugins use case)
/*static*/ std::vector<void(*)(IContext*)> GameEngine::modules;
class Context : public IContext
{
public:
void print(int) override {...}
void setThreadContext() override
{
g_ctx = this; // sets context for this module (where setThreadContext is compiled)
for(auto setDllContext : GameEngine::modules)
{
setDllContext(this); // sets context for module where setDllContext was compiled
}
}
};