是否有可能多个动态链接库(DLL)从静态库(LIB)共享线程本地存储

时间:2014-01-03 21:59:51

标签: c++ multithreading dll static-libraries thread-local-storage

我有一个由许多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共享?

2 个答案:

答案 0 :(得分:6)

这是完全正常的。每个DLL都有自己的库代码和数据副本。它自己的线程本地状态。

你可以通过假设它按你期望的方式工作来解释这个。然后两个DLL可能会意外地在不同的DLL之间共享自己的线程局部变量。显然这将是灾难性的。它无法以这种方式工作,因为没有机制可以跨模块共享TLS实例。槽索引有意保持模块专用,没有机制来获取__declspec(线程)变量的槽索引。显式调用TlsAlloc()并共享索引将是一种解决方法。不要去那里。

答案 1 :(得分:0)

虽然作者接受了Hans Passant的回答,但其中没有明显的解决方案。所以这就是我想出来的。不是很优雅,但将部分代码切割成Common.dll可能更糟/更难看。

COMMON.H

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);
    }
};

ChildDLL.cpp

#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();
    ...
}

main.cpp(root exe或dll)

#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
        }
    }
};