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