【问题标题】:Is it possible for multiple Dynamic Link Libraries (DLL) to share Thread Local Storage from a Static Library (LIB)多个动态链接库 (DLL) 是否可以从静态库 (LIB) 共享线程本地存储
【发布时间】:2014-01-03 21:59:51
【问题描述】:

我有一个由许多 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 中定义的函数,则 s_threadValues 的值将与从 Child.dll 调用相同的函数时的值不同。

由于两个 DLL 都从同一个线程访问此 TLS,我希望 TLS 是共享的,但事实并非如此。

现在,如果我将 Common.lib 更改为动态链接(例如 Common.dll),则此问题不会不再发生:对于 Root.dll 和 Child.dll,s_threadValues 是相同的。

这是预期的行为吗?无论如何在使用它的动态库之间共享静态库中定义的 TLS 共享?

【问题讨论】:

  • 单身人士在这里很有用,但如果你想让你的班级保持静态,那就不能很好地工作(你必须为你的单身人士公开一个 setter - 它很hacky但它​​有效)。不过,我希望有更好的解决方案。
  • AFAIK,预期行为:“全局”变量仅对模块是全局的(在 Windows 意义上)。要在模块之间共享一个,您必须将其存储在单个模块中并让其他人调用它,或者找到另一种存储(我个人使用基于 processID 名称的文件映射)
  • @Warty 您是说,与其将 s_threadValues 作为全局变量,不如将其作为 Singleton 的成员(而不是 TLS 值)?在这样做时,它最终不会在线程之间共享(它首先试图避免这种情况)吗?
  • 静态库完全不相关。它们不以任何形式或形式存在于可执行程序中。它们只是目标文件的容器。您最好永远不要创建任何静态库并直接使用目标文件。
  • 1.是的。 2. 让我再试一次。可执行代码中没有静态库。它们只是目标文件的容器,而目标文件只是目标代码的容器。链接器使用该目标代码来构建模块(.dll 和 .exe 文件)。您也可以直接使用目标文件,而无需创建静态库,并构建完全相同的模块。这就是为什么您使用静态库这一事实无关紧要。重要的是您是否在一个模块中有一个变量副本,或者在两个模块中有两个这样的副本。

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


【解决方案1】:

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

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

【讨论】:

  • 总结:将“通用”库保留为自己的独立 DLL 是正确的做法。睡个好觉。
【解决方案2】:

虽然作者接受了 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(根 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
        }
    }
};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-29
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-25
    • 1970-01-01
    • 2011-03-16
    相关资源
    最近更新 更多