5,276,801 members and growing! (16,549 online)
Email Password   helpLost your password?
General Programming » Threads, Processes & IPC » General     Intermediate

Create thread from global\static void returning function with any type and number of arguments.

By Lone Developer

This is a macro which takes the name of any void returning global\static procedure, followed by the list of arguments to be passed to that procedure and invokes the procedure with arguments supplied, in a new thread
C, ASM, VC6, VC7, VC7.1, VC8.0, C++Windows, NT4, WinXPVS2005, VS6, VS, Design, Dev

Posted: 9 Nov 2007
Updated: 9 Nov 2007
Views: 3,619
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
2 votes for this Article.
Popularity: 0.60 Rating: 2.00 out of 5
1 vote, 50.0%
1
0 votes, 0.0%
2
1 vote, 50.0%
3
0 votes, 0.0%
4
0 votes, 0.0%
5
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

Introduction

This 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) 

It would be much more better in case we could have an API that could create thread from any function returning any thing with any number and type of parameters

Unfortunately, this is very tough because the scope of problems is very wide, we have simple functions, returning some object\reference\pointer\void, they could then take some arguments\reference\pointers and the number of arguments could vary with each function.

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 scenes

The 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 code

Using 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. :)


License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Lone Developer



Occupation: Web Developer
Location: India India

Other popular Threads, Processes & IPC articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 4 of 4 (Total in Forum: 4) (Refresh)FirstPrevNext
Subject  Author Date 
GeneralBoost threadsmemberJarl Lindrud13:11 10 Nov '07  
GeneralPlease use (boost) bind and/or mem_fn for thismemberowillebo5:19 10 Nov '07  
Generalmacro question [modified]memberdgendreau11:40 9 Nov '07  
AnswerRe: macro questionmemberLone Developer18:06 9 Nov '07  

General General    News News    Question Question    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

PermaLink | Privacy | Terms of Use
Last Updated: 9 Nov 2007
Editor:
Copyright 2007 by Lone Developer
Everything else Copyright © CodeProject, 1999-2008
Web18 | Advertise on the Code Project