一种通过函数指针调用未确定原型的函数时参数传递的方法

一种通过函数指针调用未确定原型的函数时参数传递的方法
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)
{
    return h * e;
}

 

// 线程2
int Xie(int x, int i, int e)
{
    return x + i + e;
}

创建两个线程He和Xie,用传统方法,不解释,你懂的.


如果_beginthread可以像printf那样,支持可变参数就好了.但printf是对参数进行解释来实现可变参数的,除去效率原因,目的也与我们的不大一样.本文将要达到以下这样的效果.

void Turf(void)
{
//create_thread是本文即将实现的函数
    create_thread(He, 2, 3);
    create_thread(Xie, 1, 2, 3);
}
OK,看起来还是非常和谐的.

 

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',中间去掉了一些 检查,主要框架不变,读者可以 根据需要自行修改.
HANDLE _create_thread(THREAD_FUNCTION tf, unsigned int ss, ...)
{
    _ptiddata ptd = NULL;
    HANDLE thdl = NULL;
    if(tf)
    {
        __set_flsgetvalue();

 

        //取得参数地址
        va_list ta = NULL;
        va_start(ta, ss);

 

        ptd = (_ptiddata)_calloc_crt(1, sizeof(_tiddata));
        if(ptd)
        {
            _initptd(ptd, _getptd()->ptlocinfo);
            ptd->_initaddr = (void *)tf;
            ptd->_initarg = ta;

 

                    //保存调用函数(比如上述的Turf()函数)的ESP,用以计算栈帧大小
            DWORD caller_esp = NULL;
            __asm
            {
                MOV EAX, DWORD PTR[EBP]
                MOV caller_esp, EAX
            }

            //为何要使用EVENT,后面会说到.

            HANDLE he = ::CreateEvent(NULL, FALSE, FALSE, NULL);

            if(he)
            {

                            //使用保留字段,后果未知,建议采用更安全的做法.
                ptd->_reserved1 = (void *)caller_esp;
                ptd->_reserved2 = (void *)he;

                //开始真正的创建线程,注意call_thread

                thdl = ::CreateThread(NULL, ss, call_thread, (LPVOID)ptd, CREATE_SUSPENDED, (LPDWORD)&(ptd->_tid));
                if(thdl)
                {
                    ptd->_thandle = (unsigned long)thdl;
                    if(::ResumeThread(thdl) == (DWORD)(-1L))
                    {
                        goto LL_ERROR_RESUME;
                    }

                                    //等待线程已经拷贝栈帧,否则如果_create_thread 函数立即退出,栈帧可能被破坏,线程拷贝错误的数据.
                    ::WaitForSingleObject(he, INFINITE);
                    ::CloseHandle(he);
                }
                else
                {
LL_ERROR_RESUME:
                    ::CloseHandle(he);
                    goto LL_ERROR;
                }
            }
            else
                goto LL_ERROR;
        }
        else
        {
LL_ERROR:
            thdl = (HANDLE)-1;
            _free_crt(ptd);
        }
    }
    return thdl;
}

 

unsigned long WINAPI _call_thread(void *ptd)
{
    unsigned long r = 0L;
    _ptiddata _ptd;
    __set_flsgetvalue();
    if((_ptd = (_ptiddata)__fls_getvalue(__get_flsindex())) == NULL)
    {
        if(!__fls_setvalue(__get_flsindex(), ptd))
        {
            ::ExitThread(GetLastError());
        }
    }
    else
    {
        _ptd->_initaddr = ((_ptiddata)ptd)->_initaddr;
        _ptd->_initarg = ((_ptiddata)ptd)->_initarg;
        _ptd->_thandle = ((_ptiddata)ptd)->_thandle;
        _ptd->_reserved1 = ((_ptiddata)ptd)->_reserved1;
        _ptd->_reserved2 = ((_ptiddata)ptd)->_reserved2;
        _freefls(ptd);
        ptd = _ptd;
    }
    DWORD caller_esp = (DWORD)((_ptiddata)ptd)->_reserved1;
    DWORD tf = (DWORD)((_ptiddata)ptd)->_initaddr;
    DWORD callee_argument =  (DWORD)((_ptiddata)ptd)->_initarg;
    UINT caller_stack_frame_size = (UINT) caller_esp - (UINT)callee_argument;
    __asm
    {
        //保存ESP

        MOV EBX, ESP

        //开始 贝栈帧

        //计数
        MOV ECX, caller_stack_frame_size
        //源地址
        MOV ESI, callee_argument
        //目的地址,有一个假设ESP 大于ECX
        MOV EAX, EBX
        SUB EAX, ECX
        MOV EDI, EAX
        //拷贝
        REP MOVSB

        //结束拷贝栈帧

        //ESP指向新的栈帧

        MOV ESP, EAX
    }
    //已经完成 拷贝栈帧,create_th read 可以安全的退出.

    ::SetEvent((HANDLE)(((_ptiddata)ptd)->_reserved2));

    __asm
    {

          //调用线程
        CALL tf
        //恢复ESP

        MOV ESP, EBX
        //保存返回值

        MOV r, EAX
    }
    _destroy_thread();
    return r;
}

 

void _destroy_thread(void)
{
    return _endthread();
}

原理应该还是说明白了,余下的读者亲自调试就会清楚.

 

4 附赠

 


关于导出一组函数作为接口,通过索引进行调用,做过的读者应该明白,应用同样的原理同样可以达到.有空再整理出例子.

我还没有找出其它的应用,如果您有其它方面的应用,欢迎指出.

 

5 版本


2011年03月18日第一次编写.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值