缘由:
在给游戏做背景音乐类的时候,遇到了一个问题,需要用到MCI函数,但是MCI函数必须需要一个窗口来获得MCI执行的状态。特别在播放音乐完成的时候,MCI会向
一个窗口发送播放完成消息。所以我需要创建一个窗口。窗口就肯定有窗口过程,而窗口过程是不能够为类成员函数的。但这个类中为了保证类的封装性,这个窗口过程函数又必须为
类成员函数(因为需要访问很多变量)。因此,就想有没有办法让类成员函数也作为窗口类的回调函数呢?
Thunk技术
在网上查了些资料,发现thunk技术是可以实现这一点。下面是一些thunk技术的理解
之所以能实现成员函数作为回调函数 是因为
a,一般调用C++的成员函数之前,都是使用ECX寄存器保存对象的指针,也就是this指针。
b,C++成员函数的调用约定__thiscall的参数压栈顺序和堆栈平衡的维护 都是和回调函数的调用约定__stdcall一样.
c,ECX寄存器一般不保存有用的信息(除this指针),因此我们可以覆盖ECX寄存器中的内容
基于这三点,只要我们能够将this指针保存在ECX寄存器后 然后跳到成员函数的入口点,就可以实现将类成员函数作为回调函数用了
代码实现:
1
|
thunk.h 文件
|
1
2
3
4
5
6
7
8
|
#include"TCHAR.h"
#include<iostream>
#include<string>
#include<windows.h>
using
namespace
std;
//设定对齐
#pragma pack(push,1) //强制编译器,使数据按字节边界对齐。 <br> //默认情况下VC6.0是按4字节对齐,VC7.0按8字节对齐 <br> //指令本不是按双字边界对齐的,所以必须使其按字节边界对齐,否则出错
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
//定义一个结构体 表示thunk
struct
Thunk
{
BYTE
op_movecx;
//
DWORD_PTR
val_ecx;
BYTE
op_call;
DWORD_PTR
val_address;
};
#pragma pack(pop)
typedef
LRESULT
(*pfaCallBack)(
HWND
hWnd,
UINT
uMsg,
WPARAM
wParam,
LPARAM
lParam);
class
CThunk
{
public
:
CThunk(){};
//构造函数
~CThunk(){};
//析构函数
LRESULT
MyProc(
HWND
hWnd,
UINT
uMsg,
WPARAM
wParam,
LPARAM
lParam);
//类成员函数,我们将会让这个类成员函数作为窗口的窗口过程
public
:
void
InitThunk();
//初始化函数
pfaCallBack GetEntry()
{
return
(pfaCallBack)&m_thunk;
}
private
:
Thunk m_thunk;
};
|
thunk.cpp文件
#include"thunk.h" void CThunk::InitThunk() { m_thunk.op_movecx=0xB9;// 0xb9 mov ecx,数值 机器码 m_thunk.val_ecx=(DWORD_PTR)this;//给ecx寄存器赋值 将this赋值给ecx m_thunk.op_call=0xE9;//0xE9是Jmp 相对地址 的机器码 DWORD_PTR off=0; _asm { mov eax,CThunk::MyProc mov DWORD PTR[off],eax } m_thunk.val_address=off-((DWORD_PTR)(&m_thunk.val_address)+sizeof(DWORD_PTR));//得到成员函数的地址 } LRESULT CThunk::MyProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam) { switch(uMsg) { case WM_CLOSE: DestroyWindow(hWnd); PostQuitMessage(NULL); break; case WM_CREATE: MessageBox(NULL,_T("WM_CREATE"),_T("WM_CREATE"),MB_OK); break; default: return DefWindowProc(hWnd,uMsg,wParam,lParam); } return 0; }
在主函数中创建一个窗口 ,然后将我们类成员函数作为窗口的窗口过程函数
main.cpp文件
#include"thunk.h" //我要用这个thunk来完成一个什么问题呢 //我在主函数中创建一个窗口 //很明显这个窗口需要一个窗口过程回调函数 那么很好 我现在想将这个窗口过程的回调函数设置为一个类的成员函数 //这就是我要实现的目标 int main() { CThunk thunk; thunk.InitThunk(); HINSTANCE hInstance; hInstance=(HINSTANCE)GetModuleHandle(NULL); MSG stMsg; TCHAR szClassName[]=_T("MCI"); WNDCLASSEX stWC; ZeroMemory(&stWC,sizeof(stWC)); stWC.hCursor=LoadCursor(NULL,IDC_ARROW); stWC.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); stWC.cbSize=sizeof(stWC); stWC.style=CS_HREDRAW|CS_VREDRAW; //stWC.lpfnWndProc=DefWindowProc; stWC.lpfnWndProc=(WNDPROC)thunk.GetEntry(); //这里是关键 stWC.lpszClassName=szClassName; stWC.cbClsExtra=0; stWC.hInstance=hInstance; RegisterClassEx(&stWC); HWND hWnd; hWnd=CreateWindowEx(WS_EX_CLIENTEDGE,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,100,600,700,NULL,NULL,hInstance,NULL); if(hWnd==NULL) { MessageBox(NULL,_T("WORNG"),_T("ERROR"),MB_OK); } ShowWindow(hWnd,SW_SHOWNORMAL); UpdateWindow(hWnd); while(GetMessage(&stMsg,NULL,0,0)) { TranslateMessage(&stMsg); DispatchMessage(&stMsg); } return 0 ; }
程序执行效果:
说明成功将类成员函数作为窗口的回调函数了。
注意:
特别请注意:
typedef LRESULT (*pfaCallBack)(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);这个的定义!
刚开始的时候,我是定义为
typedef LRESULT (CALLBACK *pfaCallBack)(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
然后发现 ,虽然成员能够找到我们的类成员函数,但是闯将窗口就是不成功。并且提示为 错误码为0 创建窗口失败。
在经过了和同事一起反汇编追踪的时候终于发现了问题。
CALLBACK 的定义为__stdcall 我们模拟的是__thiscall。所以出现了问题。