Simple member thunk for threads
Tuesday, 22. January 2008, 02:35:33
Note: This sample code is prepared for Windows CE (and therefore Windows Mobile).
Start with code...
class Win32ThreadBase
{
DWORD m_dwThreadId;
HANDLE m_hThread;
LPVOID m_lpArg;
static DWORD CALLBACK ThreadEntryPoint(LPVOID lpArg)
{
return static_cast<Win32ThreadBase*>(lpArg)->ThreadEntryPoint_Thiscall();
}
DWORD CALLBACK ThreadEntryPoint_Thiscall()
{
// ... use m_lpArg
}
public:
Win32ThreadBase(LPVOID lpArg = NULL)
: m_lpArg(lpArg)
, m_hThread(NULL)
, m_dwThreadId(0)
{
}
virtual ~Win32ThreadBase()
{
// check if it's still running. it might imply
// that there might be outstanding reference to
// our thread handle. if you like, write a more
// complex class hierarchy that prevents following
// and freely passing HANDLEs around.
CloseHandle(m_hThread);
m_hThread = NULL;
}
void SetArg(LPVOID lpArg)
{
// is it a problem to mutate m_lpArg when thread is running?
assert(m_hThread == NULL);
InterlockedExchange((LPLONG)&m_lpArg, (LONG)lpArg);
}
HANDLE Create(DWORD cbStackSize = 0, DWORD dwFlags)
{
return ::CreateThread(NULL,
cbStackSize,
&Win32ThreadBase::ThreadEntryPoint,
(LPVOID)this,
dwFlags,
&m_dwThreadId);
}
// other operations
};
Caller must set lpArg to constructor of the object. This may or may not be a common implementation, but this type of implementations are used in various C++ libraries with function objects. I think it's cleaner and yet somewhat more portable than what I will discuss here.
Another approach could be introducing a tuple (pair, for example) that holds both this pointer and given void*). This could introduce some casting, and may be little memory management.
class Win32ThreadBase
{
struct ThreadDataPair
{
Win32ThreadBase* pThis;
LPVOID lpArg;
ThreadDataPair(Win32ThreadBase* p, LPVOID parg)
: pThis(p)
, lpArg(parg)
{
}
};
static DWORD CALLBACK ThreadEntryPoint(LPVOID lpArg)
{
ThreadDataPair* ppair = static_cast<ThreadDataPair*>(lpArg);
DWORD dwRet = ppair->pThis->ThreadEntryPoint_Thiscall();
delete ppair;
return dwRet;
}
...
HANDLE Create(LPVOID lpArg = NULL, DWORD cbStackSize = 0, DWORD dwFlags)
{
ThreadDataPair_t* pdata = new ThreadDataPair(this, lpArg);
return ::CreateThread(NULL,
cbStackSize,
&Win32ThreadBase::ThreadEntryPoint,
(LPVOID)pdata,
dwFlags,
&m_dwThreadId);
}
};
Yet another easy way to perform the same operation, but this has two (may be one) flaws I can see:
- Exception handling. Good news is that current standard refers to the word "thread" only in exceptions section. It's important to handle and propagate exceptions in threads correctly. This might not be relevant, because it's perfectly possible to handle and report exceptions in different threads today.
- Allocations do not respect thread boundaries. This may or may not be problem, but as a discipline, I always try to keep the contract "that who has has allocated should deallocate it".
Little thunk would make the code little different:
#if !defined(_M_ARM)
#error "Thunk is made for ARMv4. Port the thunk inside your platform machine type section."
#endif // !defined(_M_ARM)
// Thunk that simply adds a "this" pointer as a
// second argument to callback function.
// Idea taken from ATL's window thunk.
struct ThreadThunk
{
const DWORD m_mov_r1_pThis;
const DWORD m_mov_pc_proc;
DWORD m_pThis;
DWORD m_proc;
ThreadThunk()
: m_mov_r1_pThis(0xE59F1000) // mov r1, m_pThis
, m_mov_pc_proc(0xE59FF000) // mov pc, m_proc
{
}
BOOL Init(LONG proc, void* p)
{
m_pThis = (DWORD)p;
m_proc = proc;
FlushInstructionCache(GetCurrentProcess(), this, sizeof(ThreadThunk));
return TRUE;
}
operator LPTHREAD_START_ROUTINE()
{
return (LPTHREAD_START_ROUTINE)this;
}
};
// Thread implementer class. Deriving from this class is a good practice.
template<typename T>
class __declspec(novtable) ThreadImpl : public HighFrequencyObject
{
ThreadThunk m_thunk;
HANDLE m_hThread;
DWORD m_dwThreadId;
bool m_fHandleClosed;
static DWORD CALLBACK ThreadEntryPoint(LPVOID lpv, ThreadImpl<T>* pthis)
{
return static_cast<T*>(pthis)->ThreadEntryPoint_Thiscall(lpv);
}
public:
ThreadImpl()
: m_hThread(NULL)
, m_dwThreadId(0)
, m_fHandleClosed(false)
{
}
// Sets handle as closed, so that it won't be closed
// in the destructor.
inline void SetHandleClosed(bool f) { m_fHandleClosed = f; }
virtual ~ThreadImpl()
{
if (!m_fHandleClosed)
CloseHandle(m_hThread);
}
HANDLE Create(LPVOID lpv, DWORD cbStackSize = 0)
{
m_thunk.Init((LONG)&ThreadImpl<T>::ThreadEntryPoint, this);
return (m_hThread = ::CreateThread(NULL, cbStackSize, m_thunk, lpv, 0, &m_dwThreadId));
}
inline DWORD GetThreadId() { return m_dwThreadId; }
inline HANDLE GetThreadHandle() { return m_hThread; }
};
What happens is that the newly created thread will start at our thunk's address. Thunk looks like 2 instructions in memory.
mov r1, pThis // pass pThis to following function as second argument mov pc, pProc // jump to pProc
ARM calling convention standard states that first 4 function arguments passed with registers [r0-r3], it's perfectly legal for us to modify r1, no need to reset it back to its initial value. Application provided void* argument to CreateThread is passed to callback function through r0. Therefore ThreadEntryPoint's arguments will look like as in above implementation; first argument is LPVOID passed to CreateThread, second argument is this pointer we passed to thunk.









