|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionThis is a very general problem. Say i have a function like void func(int a, int b)
{
printf("\n a = %d, b = %d",a,b);
} and i need to call this function in a new thread. The simplest procedure is: 1>Make a structure like this struct funcargs { int m_a; int m_b; }; 2>Then make a dynamic object of this as funcargs *argobjptr = new funcargs; 3>Putting arguments in the object like argobjptr->m_a = a; argobjptr->m_b = b; 4>Creating another wrapper function to be used with CreateProcess or any similar method like this
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
funcargs *argobjptr = (funcargs *)lpParameter;
func(argobjptr->m_a, argobjptr->m_b);
delete argobjptr
return 0;
}
5>And at last calling this ThreadProc with previously created "argobjptr" object in CreateThread somewhat like this CreateThread( NULL, 0, ThreadProc, argobjptr, 0, NULL): so on the whole, we have to go through this whole hassle of packing arguments in a temporary wrapper object and then unpack them inside some wrapper function because, all such thread creating api's limit us to create thread with function with only this syntax: DWORD WINAPI ThreadProc(LPVOID lpParameter)
Then the function can be inline\static\class member\ virtual...... I have tried to solve this problem to some extent and i had created a macro, which just takes the function name and it's parameters (any number and type) as argument and call the function upon the given arguments in a new thread. Like for the above given function the code would simply be CALL_IN_THREAD(func, 4, 5);
Off course, this has some limitation too: First of all, the macro works on only global\static function returning void. Second, the arguments shouldn't be reference\pointer to any local variable\object (by simple logic, how could a thread can access object allocated some time back in the context\stack of some other thread ? ). Thirdly, the macro would bang, in case the number of parameters mismatches, as expected by the actual function. Fourthly, the macro is highly platform\hardware\compiler dependent. I have tested it on x86 machine with Windows NT versions(NT, 2000 and XP) on Visual Studio 6.0 and Visual Studio 2005 Compiler. In short, keep in mind that, this macro uses the same old process described above inside it to accomplish this task.
Behind the scenesThe working of this code is simple. This code works on the principle that whenever a function is called, to pass arguments to that function, they are pushed on stack first, before the call to that function would be made. So if we need to call the same function in the context of some other thread, we need to push the same arguments on the stack of that thread before calling this function there. What we can do is, we can copy all the arguments from the stack of this function, store them in a temporary heap location and paste them on the stack of new thread and then can call that function. The function would work as if called from original thread. This is exactly what we do above in a conventional approach, but the difference here is that the things are just automated (packing\unpacking of arguments and calling that function) here. But, how would we know the start and end address of stack memory area holding argument values? We can, if we can just some how calculate the address of stack pointer just before first argument (last in argument list as arguments are pushed from last in __cdecl calling convention) is passed. The end position would be 4 bytes before the address of pointer to function passed itself (which is pushed as last argument). Have a look at code. #define CALL_IN_THREAD if(CThreadWrapper tempobj=0) tempobj.FunctionCaller //Main wrapper class for calling a function in a thread. //manages variable and data types used in this process class CThreadWrapper { //Structure to store function call information struct FUNC_CALL_INFO{ FUNC_TYPE m_pFuncAddress; DWORD *m_pArgs; int m_pArgLength; }; //Stores value of stack pointer before the call of desired function DWORD m_InitialStackAddress; //Uses 'FUNC_CALL_INFO' object to call the //desired function with given arguments static DWORD WINAPI ThreadFunc( LPVOID lpParam ); public: CThreadWrapper(const int); // Creates and object of 'FUNC_CALL_INFO' to be used in ThreadFunc void FunctionCaller(LPTHREAD_INFORMATION p_pthreadinfo, FUNC_TYPE func_address, ...); //helper function for MACRO with if condition __forceinline operator const bool(){return true;} }; /* Constructor function. in addition it also calculate the address of stack pointer before the call to this function */ CThreadWrapper::CThreadWrapper(const int) { DWORD ebpvalue; __asm mov ebpvalue, ebp; m_InitialStackAddress = ebpvalue + 12; #ifdef _DEBUG printf("\n m_InitialStackAddress = 0x%08X",m_InitialStackAddress); #endif } /* This functions does following tasks: 1>Copies the arguments passed to a temperory buffer a> calculate address of last argument b> subtract address of last argument from first to calculate number of arguments c>allocate a new buffer of the size of all arguments d>copies all the arguments to buffer 2>Put all information in a wraaper object 'FUNC_CALL_INFO' 3>Call CreateThread on 'ThreadFunc' */ void CThreadWrapper::FunctionCaller(LPTHREAD_INFORMATION p_pthreadinfo, FUNC_TYPE func_address, ...) { DWORD CurrentStackAddress = (DWORD)&func_address; // _asm mov CurrentStackAddress, ebp; #ifdef _DEBUG printf("\n FunctionAddress = 0x%08X",func_address); #endif #ifdef _DEBUG printf("\n CurrentStackAddress = 0x%08X",CurrentStackAddress); #endif //Calculate no of arguments passed in 'DWORD' DWORD arglen = ( ( m_InitialStackAddress - CurrentStackAddress ) - OTHER_STACK_DATA_SIZE ) / STACK_DATA_SIZE; #ifdef _DEBUG printf("\n Number of Arguments = %d",arglen); #endif //Allocate memory on heap to copy arguments from stack DWORD *args = (DWORD *) malloc( arglen * STACK_DATA_SIZE ); //Set pointer to argument on stack DWORD *argadd = (DWORD *)(CurrentStackAddress + OTHER_STACK_DATA_SIZE); //Copy arguments on heap for( unsigned int index=0; index < arglen; argadd++,index++) *(args + index) = *argadd; //Wrap up address of function to call and arguments in a object FUNC_CALL_INFO *finfo = (FUNC_CALL_INFO *)malloc(sizeof(FUNC_CALL_INFO)); finfo->m_pFuncAddress = func_address; finfo->m_pArgLength = arglen; finfo->m_pArgs = args; //Create a thread with 'ThreadFunc' as wrapper function p_pthreadinfo->hThread = CreateThread( NULL, 0, CThreadWrapper::ThreadFunc, finfo, 0, &(p_pthreadinfo->dwThreadId)); } /* This functions does following tasks: 1>fetch information from FUNC_CALL_INFO object a> fetch all arguments b> fectch function to be called 2>Push arguments into stack to be used by function to be called 3>Call the function 4>Adjust stack 5>free up allocated memory */ DWORD WINAPI CThreadWrapper::ThreadFunc( LPVOID lpParam ) { //Retrieve Function Call information FUNC_CALL_INFO *finfo = (FUNC_CALL_INFO *)lpParam; DWORD *args = finfo->m_pArgs; DWORD arglen = finfo->m_pArgLength; FUNC_TYPE funcadd = finfo->m_pFuncAddress; //Push all arguments to stack long index = arglen-1; unsigned int value = 0; unsigned stackdisp = arglen*sizeof(DWORD); while( index>=0 ) { value = *( args + index ); index--; __asm push value; } //Call actual function __asm call funcadd; //Rectify stack __asm add esp,stackdisp; //Free up memory free(finfo->m_pArgs); free(finfo); return 0; } Using the codeUsing the code is easy. Just look at the following test code:bool complete = false; void func(char u, char e, int a, int b) { while(complete == false) printf("a = %d, b = %d\n",a,b); int t=7, y= 8; int c = t+y; } void func1(int a, int b) { Sleep(2000); printf("\n\n\n\n\na = %d, b = %d\n\n\n\n\n\n",a,b); complete = true; } int main(int argc, char* argv[]) { printf("Hello World!\n"); THREAD_INFORMATION t; //func & func1, would be called in a thread. for(int i=0;i<3;i++) CALL_IN_THREAD(&t, (FUNC_TYPE)func, 'U', 'A', 7+i, 8*i); CALL_IN_THREAD(&t, (FUNC_TYPE)func1, 4, 5); while(complete == false){}; getchar(); return 0; }Here functions 'func' and 'func1' has been invoked in a different thread. THREAD_INFORMATION object stores the handle and threadid of created threads, but it hasn't been used here. What next???Next, i'll try to make this MACRO work on class member functions.Your help and suggestion would be highly appreciated. :)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||