Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C++

Switching threads

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
16 Sep 2007CPOL17 min read 64.7K   911   39  
How to switch the thread that a routine is running on.
#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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Microsoft
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions