最近一直在研究WTL、MFC的消息封装机制,这里面用到了两个核心技术:
1. 成员函数指针;
其实要实现一个类似于WTL的消息封装机制只需要300行代码左右,成员函数指针的调用效率要低于普通的函数指针,原因是要计算this指针,这就涉及到重载,虚函数表等。我在使用中主要发现两个要点,第一个是成员函数指针的大小是可变的,(当然也可以通过设置编译选项来使得总是使用最大值,但是缺省的是优化的选项),当函数有虚函数表的时候指针大小要大一些。(其实在C++里面只有在很少的情况下sizeof(pointer)不等于4的,这个就是其中之一。如果不小心这个指针大小的变化的话,从派生类的非静态成员函数指针转换到基类的成员函数指针的时候可能就crash。另外还需要小心virtual继承和多重继承,所以MFC、WTL中的窗口类也是不支持多重继承的。关于MS如何实现类的编译可以参考非常古老的文章:http://msdn.microsoft.com/archive/en-us/dnarvc/html/jangrayhood.asp,当然这块的编译技术已经多年没有大的更新过了,所以还是管用的。
2. 动态修改程序栈。
这个是为了传递this指针给WndProc全局函数,这个是C回调函数,为了把this指针传进去好进行面向对象的封装,然而由于Windows的CreateWindow所带的几个userdata(包括RegisterClassWndEx结构的扩展字节)都已经被窗口系统作为特定用途了,所以没有办法,只能通过修改程序栈来实现了,这个其实不是非常好,毕竟依赖于指令集,不过WTL就是这么做的,尽管这么做也是很令人费解的。具体的方法如下:首先把一个StartWindowProc的全局函数指针传递给RegisterWndclassEx,这样当调用CreateWindow的时候,第一次回调将运行到StartWindowProc,而且在CreateWindow过程中是线程保护的,所以可以把ThreadID和this指针绑定在createwindow之前放置到全局队列里面去,到startwindowproc的时候可以通过threadid取出this指针,然后修改程序的栈,把this指针固化到代码里面去,这样下次回调的时候就可以用this指针调用新的回调函数了。所以如果要自己实现这个代码首先要记得在createwindow之前和之后加上线程锁,避免crash;其次是在destroy this指针之前一定要把消息回调函数指向一个空的地址,以免访问了空的this指针。下面是代码示例:
#if defined(_M_IX86)
#pragma pack(push,1)
struct _WndProcThunk
{
DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
DWORD m_this; //
BYTE m_jmp; // jmp WndProc
DWORD m_relproc; // relative jmp
};
#pragma pack(pop)
#elif defined (_M_ALPHA)
// For ALPHA we will stick the this pointer into a0, which is where
// the HWND is. However, we don't actually need the HWND so this is OK.
#pragma pack(push,4)
struct _WndProcThunk //this should come out to 20 bytes
{
DWORD ldah_at; // ldah at, HIWORD(func)
DWORD ldah_a0; // ldah a0, HIWORD(this)
DWORD lda_at; // lda at, LOWORD(func)(at)
DWORD lda_a0; // lda a0, LOWORD(this)(a0)
DWORD jmp; // jmp zero,(at),0
};
#pragma pack(pop)
#else
#error Only Alpha and X86 supported
#endif
#pragma pack(push,1)
struct _WndProcThunk
{
DWORD m_mov; // mov dword ptr [esp+0x4], pThis (esp+0x4 is hWnd)
DWORD m_this; //
BYTE m_jmp; // jmp WndProc
DWORD m_relproc; // relative jmp
};
#pragma pack(pop)
#elif defined (_M_ALPHA)
// For ALPHA we will stick the this pointer into a0, which is where
// the HWND is. However, we don't actually need the HWND so this is OK.
#pragma pack(push,4)
struct _WndProcThunk //this should come out to 20 bytes
{
DWORD ldah_at; // ldah at, HIWORD(func)
DWORD ldah_a0; // ldah a0, HIWORD(this)
DWORD lda_at; // lda at, LOWORD(func)(at)
DWORD lda_a0; // lda a0, LOWORD(this)(a0)
DWORD jmp; // jmp zero,(at),0
};
#pragma pack(pop)
#else
#error Only Alpha and X86 supported
#endif
class CWndProcThunk
{
public:
union
{
_AtlCreateWndData cd;
_WndProcThunk thunk;
};
void Init(WNDPROC proc, void* pThis)
{
#if defined (_M_IX86)
thunk.m_mov = 0x042444C7; //C7 44 24 0C
thunk.m_this = (DWORD)pThis;
thunk.m_jmp = 0xe9;
thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));
#elif defined (_M_ALPHA)
thunk.ldah_at = (0x279f0000 | HIWORD(proc)) + (LOWORD(proc)>>15);
thunk.ldah_a0 = (0x261f0000 | HIWORD(pThis)) + (LOWORD(pThis)>>15);
thunk.lda_at = 0x239c0000 | LOWORD(proc);
thunk.lda_a0 = 0x22100000 | LOWORD(pThis);
thunk.jmp = 0x6bfc0000;
#endif
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));
}
};
{
public:
union
{
_AtlCreateWndData cd;
_WndProcThunk thunk;
};
void Init(WNDPROC proc, void* pThis)
{
#if defined (_M_IX86)
thunk.m_mov = 0x042444C7; //C7 44 24 0C
thunk.m_this = (DWORD)pThis;
thunk.m_jmp = 0xe9;
thunk.m_relproc = (int)proc - ((int)this+sizeof(_WndProcThunk));
#elif defined (_M_ALPHA)
thunk.ldah_at = (0x279f0000 | HIWORD(proc)) + (LOWORD(proc)>>15);
thunk.ldah_a0 = (0x261f0000 | HIWORD(pThis)) + (LOWORD(pThis)>>15);
thunk.lda_at = 0x239c0000 | LOWORD(proc);
thunk.lda_a0 = 0x22100000 | LOWORD(pThis);
thunk.jmp = 0x6bfc0000;
#endif
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk));
}
};
要是这里没讲清楚,抱歉啦,实在比较难讲……可以参考boost.function还有C++ delegate