怎么将c++的成员函数转为静态函数?
前几天读了一位网友的文章《如何让API回调你的VC类成员函数而不是静态函数》。写得很好,到底是高手!可惜也因为是高手,很多细节都略过不谈了(高手嘛,你知道的,懒得说细节了:),所以我决定写这篇小文作为原文的补充。另外,本文写了一个独立的例子,示范了怎么将类成员函数转为消息函数传给windows。例子文件的链接: testwin 。
一共有4个补充:
1.成员函数调用与普通函数调用有什么区别?
区别是:调用成员函数时,ecx寄存器保存的是this指针。
2.关键代码段:
抛开乱七八糟的封装,让我把关键代码段展示给大家。整篇文章的核心技术实际上就是下面两个关键代码段。
1)代码段1:
代码段1包含了一条指令和两个指针。代码段对应着结构体_ACCallbackOpCodes。
在本文的例子中
wcex.lpfnWndProc = cb;//就是窗口消息函数的地址
实际上保存的是指向“代码段1”的地址。
1)call 偏移量 //“偏移量”指向的是代码段2
2)LONG_PTR _this;//这个指针里保存了this指针
3)LONG_PTR _func;//这个指针里保存了成员函数的指针
2)代码段2:
代码段2包含了4条指令。代码段2对应着STDACJMPProc函数。
1)POP ECX
//将栈里保存的this指针的偏移量pop到ecx里去
//你可能会觉得奇怪:this指针怎么会跑到栈里去了?
//呵呵,怪自己汇编太差吧:P
//这是因为call指令会把它的下一条指令压入栈呀!
//这也是为什么要把this指针放在call指令后面的原因。
//对于call的详细信息请参考汇编语言手册吧。
2)MOV EAX, DWORD PTR [ECX + 4] //成员函数的指针存入eax
3)MOV ECX, [ECX] //this指针存入ecx
4)JMP EAX //跳转到成员函数的起始地址!大功告成!
3.为什么要用VirtualAlloc函数?
先
举一个反例:如果在栈上分配一块内存,保存_ACCallbackOpCodes,那么就需要调用FlushInstructionCache函数,因为
_ACCallbackOpCodes需要动态更新某些参数,而cpu不会自动刷新这块内存(这样就必须调用FlushInstructionCache
来刷新)。但是如果用VirtualAlloc就不会有这个问题。
4.在CalcJmpOffset中,为什么要计算STDACJMPProc(也就是“代码段2”)相对于_ACCallbackOpCodes的偏移量?
我们知道_ACCallbackOpCodes的第一条指令是:
call STDACJMPProc(也就是“代码段2”);
但
是因为_ACCallbackOpCodes与“代码段2”不在同一个段,在执行call指令时,cs寄存器指向的是
_ACCallbackOpCodes所在段的段基址,所以想要跳转到“代码段2”上,就必须计算“代码段2”相对于
_ACCallbackOpCodes所在段的段基址的偏移量(这个段基址就是_ACCallbackOpCodes的首地址)。