一种通过函数指针调用未确定原型的函数时参数传递的方法
A method of parameter passing while calling an unknown prototype function using a function pointer
作者:netbrain
0 引言
如果提供函数原型,我们可以通过函数指针来调用函数并传递参数.但当被调用函数原型是未确定时,如何进行参数传递呢.传统的做法是将所有的参数放在一个结构里,并将其指针作为参数传递.例如_beginthread函数,如此做法显得不够自然和优雅.再有一个明显的例子时导出一组函数作为接口,通过索引进行调用,所有的函数原型不得不全部一样.
本文提出一种方法,可以解决上述或者类似问题.文中讲述使用VC/VS2008,以Win32和IA-32为平台,感谢他们.
1 目的
首先用实例来说明问题.
// 线程1 int He(int h, int e)
// 线程2 |
如果_beginthread可以像printf那样,支持可变参数就好了.但printf是对参数进行解释来实现可变参数的,除去效率原因,目的也与我们的不大一样.本文将要达到以下这样的效果.
void Turf(void) { //create_thread是本文即将实现的函数 create_thread(He, 2, 3); create_thread(Xie, 1, 2, 3); } |
2 原理
背景知识简单说明一下.
上面为一个栈说明图,由于未确定例如 He 函数的参数个数,所以create_thread不知如何压栈参数去调 用He . 不过我们可以手动构建一个栈帧,只要He 的参数全部在栈帧内即可以 .恰巧的是,EBP 保存了调用函数的ESP ,而从参数1到调用函数的ESP 的这段空间内 , 一定是包含了所有的参数,我们把这一段栈帧,拷贝到栈顶,然后调用He ,剩下的事He 会自己去做了,虽然这样会多拷贝一些数据.
3 实现
直接给代码,中间注释.
typedef void (__cdecl *THREAD_FUNCTION)();
//create_thread实际上使用 _create_thread,它的一个参数 stack_size一般不需要使用. #define create_thread(tf, ...) _create_thread((THREAD_FUNCTION)tf, 0, __VA_ARGS__)
//主角登场了,实际上 _create_thread以及后面的函数,移植自 `VC/crt/src/thread.c',中间去掉了一些 检查,主要框架不变,读者可以 根据需要自行修改.
//取得参数地址
ptd = (_ptiddata)_calloc_crt(1, sizeof(_tiddata));
//保存调用函数(比如上述的Turf()函数)的ESP,用以计算栈帧大小 //为何要使用EVENT,后面会说到. HANDLE he = ::CreateEvent(NULL, FALSE, FALSE, NULL); if(he) //使用保留字段,后果未知,建议采用更安全的做法. //开始真正的创建线程,注意call_thread . thdl = ::CreateThread(NULL, ss, call_thread, (LPVOID)ptd, CREATE_SUSPENDED, (LPDWORD)&(ptd->_tid)); //等待线程已经拷贝栈帧,否则如果_create_thread 函数立即退出,栈帧可能被破坏,线程拷贝错误的数据.
unsigned long WINAPI _call_thread(void *ptd) MOV EBX, ESP //开始拷 贝栈帧 //计数 //结束拷贝栈帧 //ESP指向新的栈帧 MOV ESP, EAX ::SetEvent((HANDLE)(((_ptiddata)ptd)->_reserved2)); __asm //调用线程 MOV ESP, EBX MOV r, EAX
void _destroy_thread(void) |
原理应该还是说明白了,余下的读者亲自调试就会清楚.
4 附赠
关于导出一组函数作为接口,通过索引进行调用,做过的读者应该明白,应用同样的原理同样可以达到.有空再整理出例子.
我还没有找出其它的应用,如果您有其它方面的应用,欢迎指出.
5 版本
2011年03月18日第一次编写.