使用全局/静态 void 返回函数创建线程,该函数接受任意类型和数量的参数
这是一个宏,它接受一个返回 void 的全局/静态过程的名称,然后是传递给该过程的参数列表,并在新线程中调用带有提供参数的过程。
引言
这是一个非常普遍的问题。比如,我有一个函数像这样
void func(int a, int b)
{
printf("\n a = %d, b = %d",a,b);
}
我需要在新线程中调用这个函数。
最简单的程序是
- 制作一个这样的结构
- 然后像这样动态创建一个该结构的实例
- 像这样将参数放入对象中
- 创建一个另一个包装函数,用于与
CreateProcess
或任何类似方法一起使用,如下所示 - 最后,像这样使用之前创建的“
argobjptr
”对象调用ThreadProc
来创建线程
struct funcargs
{
int m_a;
int m_b;
};
funcargs *argobjptr = new funcargs;
argobjptr->m_a = a;
argobjptr->m_b = b;
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
funcargs *argobjptr = (funcargs *)lpParameter;
func(argobjptr->m_a, argobjptr->m_b);
delete argobjptr
return 0;
}
CreateThread( NULL, 0, ThreadProc, argobjptr, 0, NULL):
所以,总的来说,我们必须经历这种打包参数到临时包装对象的麻烦,然后在某个包装函数中解包它们,因为所有这些创建线程的 API 都限制我们只能使用具有以下语法的函数来创建线程
DWORD WINAPI ThreadProc(LPVOID lpParameter)
如果有一个 API 可以从任何返回任意内容、接受任意数量和类型的参数的函数创建线程,那就太好了。
不幸的是,这非常困难,因为问题的范围非常广泛;我们需要简单的函数,返回一些对象/引用/指针/void,然后它们可以接受一些参数/引用/指针,并且参数的数量可能因每个函数而异。
然后函数可以是内联/静态/类成员/虚拟的......
我试图在一定程度上解决这个问题,我创建了一个宏,它只接受函数名及其参数(任意数量和类型)作为参数,并在新线程中调用带有给定参数的函数。
就像对于上面的函数一样,代码将非常简单
CALL_IN_THREAD(func, 4, 5);
当然,这也有一些限制
- 首先,宏只适用于返回
void
的全局/静态函数。 - 其次,参数不应该是任何局部变量/对象的引用/指针(根据简单逻辑,一个线程如何访问在其他线程的上下文/堆栈中先前分配的对象?)。
- 第三,如果参数的数量与实际函数预期的不匹配,宏就会出错。
- 第四,该宏高度依赖于平台/硬件/编译器。我在 x86 机器上,使用 Windows NT 版本(NT、2000 和 XP)以及 Visual Studio 6.0 和 Visual Studio 2005 编译器进行了测试。
总之,请记住,这个宏在其内部使用了上述相同的旧过程来完成这项任务。
幕后
代码的工作原理很简单。
这段代码的原理是,每当调用一个函数时,为了将参数传递给该函数,它们首先被推入堆栈,然后才会调用该函数。
所以,如果我们想在另一个线程的上下文中调用同一个函数,我们需要在该线程的堆栈中推送相同的参数,然后再在那里调用这个函数。
我们可以做的是,我们可以从这个函数的堆栈中复制所有参数,将它们存储在临时的堆内存位置,然后将它们粘贴到新线程的堆栈中,然后就可以调用该函数了。函数将像在原始线程中调用一样工作。
这正是我们在传统方法中所做的,但这里的不同之处在于,(参数的打包/解包和调用该函数)这些事情只是在这里自动完成了。
但是,我们怎么知道保存参数值的堆栈内存区域的起始和结束地址呢?
我们可以,如果我们能以某种方式计算出第一个参数(在参数列表中最后一个,因为参数是根据 __cdecl 调用约定从最后一个推入的)之前的堆栈指针地址。结束位置将是传递给函数的指针地址(作为最后一个参数推入)之前 4 个字节。
看看这里的代码
#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;
}
使用代码
使用代码很简单。看看下面的测试代码
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;
}
在这里,函数 'func
' 和 'func1
' 已在另一个线程中调用。THREAD_INFORMATION
对象存储了创建线程的句柄和线程 ID,但这里没有使用它们。
接下来呢???
接下来,我将尝试让这个宏适用于类成员函数。非常感谢您的帮助和建议。:)