如果有个库函数接受的是无参数的回调函数怎么办?.
问题:
如果某个第三方库的某个函数需要接收用户传过来的回调函数,该回掉函数通常可能需要获取用户自己的某些参数。
一般做法是第三方库提供这样的回掉方式
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行调用用户指定的回掉函数
- 上一篇 Qt开发问答.
- 下一篇 Mfc message routine.