如果有个库函数接受的是无参数的回调函数怎么办?

问题:

如果某个第三方库的某个函数需要接收用户传过来的回调函数,该回掉函数通常可能需要获取用户自己的某些参数。

一般做法是第三方库提供这样的回掉方式

void LibRegisterFtn(void(*f)(void *), void *param)

用户通过void指针来传入自己感兴趣的参数

比如例子

class UserClass

{

    UserClass()
    {

     LibRegisterFtn(
    }

    static void callback(void *p)
    {
        UserClass *pThis = (UserClass*)p;
        pThis->xxxx();
    }

};

 

但如果该第三方库写的比较恶心,没有提供这个二外的参数怎么办呢?

比如这个

void LibRegisterFtn(void(*f)())

针对这种情况,大家常见的做法是定义一个全局变量,通过这个全局变量将参数传入到回掉函数里面

UserClass g_me;

void callback()
{
    g_me.xxxx();
}

这种做法的恶心之处在于多了一个全局变量,如果在多线程情况下,每个线程注册不同的参数到回掉函数就需要定义多个全局变量。每个线程接受相应的变量

大家都知道,全局变量很不鼓励的一种做法

 

现在,俺介绍一种新的方法,可以把任意局部变量作为参数传入到该回掉函数。其实这种做法是参考了wtl窗口消息会掉的做法。具体来说就是动态生成一段回掉函数,从而该回掉函数可以通过offset一个固定的偏移来获取到需要的参数

废话不多说,代码如下

#include <stdio.h>
#include <windows.h>
#include <assert.h>
#include <functional>

typedef void(*F)();

void LibFtn(F callback)
{
    callback();
}

class MyStruct
{
public:
    void init(std::function<void()>    f)
    {
        f_ = f;
        init(this, callback);
    }

    F get()
    {
        F f = (F)(dummy + 8);
        return f;
    }

private:
    char dummy[0x20];
    std::function<void()>    f_;

    typedef  void(_stdcall *_myCallbackSpecial)(void *n);

    

    static void _stdcall callback(void *n)
    {
        MyStruct *pThis = (MyStruct*)n;
        pThis->f_();
    }

    void init(void *p, _myCallbackSpecial fCallback)
    {
        char dummy2[0x20] = { 0x90, 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0x90 , 0xe8 , 0x00 , 0x00 , 0x00 , 0x00 , 0x58 , 0x83 , 0xe8
            , 0x0d, 0x50, 0x83, 0xc0, 0x04, 0x8b, 0x00, 0xff, 0xd0, 0xc3, 0x83, 0xc4, 0x04, 0x3b, 0xec, 0xe8 };
        memcpy(dummy, dummy2, sizeof(dummy2));

        DWORD lOldProtect;
        BOOL bRet = VirtualProtect(dummy, sizeof(dummy), PAGE_EXECUTE_READWRITE, &lOldProtect);
        assert(bRet);
        FlushInstructionCache(GetCurrentProcess(), dummy, sizeof(dummy));

        memcpy(dummy, p, 4);
        memcpy(dummy + 4, &fCallback, 4);
    }
};

void main()
{
    MyStruct ms;

    int i = 10;
    ms.init([=]() {
        printf("Hello %d\n", i);
    });

    LibFtn(ms.get());
}

该u代码关键就是那堆字符串,其assembly对应的代码如下

 
1 005EF99C  call        005EF9A1  
2 005EF9A1  pop         eax  
3 005EF9A2  sub         eax,0Dh  
4 005EF9A5  push        eax  
5 005EF9A6  add         eax,4  
6 005EF9A9  mov         eax,dword ptr [eax]  
7 005EF9AB  call        eax  
8 005EF9AD  ret  

第一,2行用来获取eip地址,第3,4行将参数push进堆栈,第5,6行调用用户指定的回掉函数

 

posted on 2017-03-11 23:23  cutepig  阅读(436)  评论(0编辑  收藏  举报

导航