|
#ifndef __TSWITCH_H__
#define __TSWITCH_H__
#pragma once
//
// we support only 32-bit x86
//
#ifndef _M_IX86
#error This file can be included only in projects that target 32-bit x86.
#endif
//
// the window message that is sent from the worker thread
// to the UI thread to have the thread switched
//
const int WM_INVOKE = RegisterWindowMessage( _T( "__ThreadSwitch__" ) );
//
// define _ASSERT if its not defined
//
#ifndef _ASSERT
#ifdef ASSERT
#define _ASSERT(e) ASSERT(e)
#elif ATLASSERT
#define _ASSERT(e) ATLASSERT(e)
#else
#ifdef _DEBUG
#define _ASSERT(e) if( !(e) ){ __asm int 3; }
#else
#define _ASSERT(e) __noop
#endif // _DEBUG
#endif // ASSERT
#endif // _ASSERT
//
// Specifies the calling convention being used by the routine
// that's switching threads.
//
enum CallingConvention
{
ccStdcall,
ccCdecl
};
struct ThreadSwitchContext
{
DWORD_PTR Address; // the function which is to be called back from the
// UI thread
DWORD_PTR Ebp; // the value of the EBP register from the worker thread
BYTE ParamsCount; // the number of parameters that this routine requires
CallingConvention Conv; // calling convention used by this function
ThreadSwitchContext( DWORD_PTR addr, DWORD_PTR ebp,
BYTE count, CallingConvention conv ) :
Address( addr ), Ebp( ebp ),
ParamsCount( count ), Conv( conv )
{
}
DWORD_PTR Invoke()
{
//
// skip previous EBP and EIP to get to the param data
//
Ebp += ( sizeof( DWORD_PTR ) * 2 );
//
// push all the params starting with the first
//
LPBYTE params = (LPBYTE)Ebp;
for( int i = ParamsCount - 1 ; i >= 0 ; --i )
{
DWORD_PTR param = *( (PDWORD_PTR)( params + ( i * sizeof( DWORD_PTR ) ) ) );
__asm push param;
}
//
// now call the function
//
DWORD_PTR address = Address;
__asm call address;
//
// save the return value
//
DWORD_PTR dwEAX;
__asm mov dwEAX, eax;
//
// compute the size of the stack that's been used
// for the function parameters
//
DWORD stack = ( ParamsCount * sizeof( DWORD ) );
//
// clear the stack if the calling convention is cdecl;
// with stdcall the callee would have done this for us
//
if( Conv == ccCdecl )
{
__asm add esp, stack;
}
return dwEAX;
}
//
// This function retrieves the EBP register value of the caller. In
// a typical release build, this gets inlined in which case there's no
// separate stack frame for "GetEBP" itself that we need work over.
//
// In a debug build "GetEBP" does not get inlined and we need to fetch
// the caller's EBP value which luckily for us, is the first item that
// gets pushed on to the stack in the routine's prologue.
//
// It would have been better if we could just make this a part of the
// THREAD_SWITCH_* macros but the preprocessor does not seem to like
// embedded assembler statements in macros very much!
//
static DWORD_PTR GetEBP()
{
DWORD_PTR dwEBP;
__asm mov dwEBP, ebp;
#ifdef _DEBUG
//
// return the caller's EBP
//
return *( (PDWORD_PTR)dwEBP );
#else
//
// "GetEBP" gets inlined in release builds so we just
// return the current EBP
//
return dwEBP;
#endif
}
};
//
// This macro determines whether a thread switch operation is
// required by comparing the current thread ID with the ID of the
// thread that created the given window handle.
//
#define InvokeRequired(hwnd) ( GetCurrentThreadId() != \
GetWindowThreadProcessId( hwnd, NULL ) )
//
// The THREAD_SWITCH_* macros generate the code required for switching
// context on a need basis.
//
//
// The THREAD_SWITCH_WITH_RETURN macro is used with functions that return
// a value. It accepts the following parameters:
//
// hwnd - handle to the window on whose thread this function
// is to execute
// fn - address of the function that is to be executed in the
// UI thread
// params - count of the parameters that this routine accepts
// conv - the calling convention used by this function; value
// must belong to the "CallingConvention" enum
// type - the type of the value returned by this function
//
// Here's an example:
//
// int __cdecl Doofus( HWND hwnd, int a, float b )
// {
// THREAD_SWITCH_WITH_RETURN( hwnd, Doofus, 3, ccCdecl, int )
// }
//
#define THREAD_SWITCH_WITH_RETURN(hwnd, fn, params, conv, type) \
if( InvokeRequired(hwnd) ) \
{ \
return (type)SendMessage( hwnd, WM_INVOKE, \
(WPARAM)new ThreadSwitchContext( (DWORD_PTR)fn, \
ThreadSwitchContext::GetEBP(), params, conv ), 0 ); \
} \
_ASSERT( InSendMessage() == TRUE );
//
// The THREAD_SWITCH macro is used with functions that do
// not return anything. It accepts the following parameters:
//
// hwnd - handle to the window on whose thread this function
// is to execute
// fn - address of the function that is to be executed in the
// UI thread
// params - count of the parameters that this routine accepts
// conv - the calling convention used by this function; value
// must belong to the "CallingConvention" enum
//
// Here's an example:
//
// void __cdecl Doofus( HWND hwnd, int a, float b )
// {
// THREAD_SWITCH( hwnd, Doofus, 3, ccCdecl )
// }
//
#define THREAD_SWITCH(hwnd, fn, params, conv) \
if( InvokeRequired(hwnd) ) \
{ \
SendMessage( hwnd, WM_INVOKE, \
(WPARAM)new ThreadSwitchContext( (DWORD_PTR)fn, \
ThreadSwitchContext::GetEBP(), params, conv ), 0 ); \
return; \
} \
_ASSERT( InSendMessage() == TRUE );
//
// This macro can be used to process the WM_INVOKE message in the
// window procedure.
//
#define THREAD_SWITCH_INVOKE( wParam ) \
ThreadSwitchContext *context = reinterpret_cast<ThreadSwitchContext *>( wParam ); \
DWORD_PTR dwReturn = context->Invoke(); \
delete context; \
return dwReturn;
#endif //__TSWITCH_H__
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.