Question

I have a game made up of many DLL files. Some of these DLLs link to the same static library (LIB).

So something like this:

Game.exe -> Root.dll -> Child.dll
               |            |
               |            '-> Common.lib (contains __declspec(thread))
               |
               '-> Common.lib (contains __declspec(thread))

Root.dll loads Child.dll which statically links Common.lib. Root also statically links Common.lib. Because Common is statically linked, it gets compiled directly into the loading dll (eg. Root and Child).

Common.lib contains a variable using Thread Local Storage (TLS).

__declspec(thread) static void* s_threadValues[PlatformThreadSlotsMax];

This results in some problematic behavior: Root.dll and Child.dll each contain a different instance of the TLS data (s_threadValues). Even on the same thread, if Root.dll calls a function defined in Common.lib, the value of s_threadValues will be different from its value if that same function is called from Child.dll.

Since both DLL are accessing this TLS from the same thread, I would expect the TLS to be shared, but it is not.

Now, if I change Common.lib to a dynamically link (eg. Common.dll) this issue does not occur anymore: s_threadValues is the same for both Root.dll and Child.dll.

Is this expected behavior? Is there anyway to have the TLS shared defined in the static lib shared between dynamic libs using it?

Was it helpful?

Solution

This is entirely normal. Each DLL has its own copy of the library code and data. And its own thread-local state.

You can reason this out by assuming it would work the way you expected. Then two DLLs could accidentally have their own thread-local variables shared between different DLLs. Clearly that would be disastrous. It can't work that way because there is no mechanism to share the TLS instance across modules. The slot indices are intentionally kept private to the module, there's no mechanism to obtain the slot index of a __declspec(thread) variable. Calling TlsAlloc() explicitly and sharing the index would be a workaround. Don't go there.

OTHER TIPS

While the author had accepted an answer by Hans Passant there is no obvious solution proposal in it. So this is what I've come up with. Not very elegant but slicing part of code into Common.dll may be even worse/ugly.

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 or 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
        }
    }
};
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top