现在的位置: 首页 > 综合 > 正文

WTL学习之旅(三)WTL中 Thunk技术本质(含代码)

2013年04月22日 ⁄ 综合 ⁄ 共 6673字 ⁄ 字号 评论关闭
文章目录

 

转载请标明是引用于 http://blog.csdn.net/chenyujing1234

 例子代码:(编译工具:VS2005)

 http://www.rayfile.com/zh-cn/files/62839154-8251-11e1-aaa6-0015c55db73d/

一、前言

通知我们要在窗口的回调函数里获得包含此窗口的类的指针得把窗口类指针放到窗口绑定的数据里,可是有一种技术可以帮助你省去这种绑定的麻烦。

这就是Thunk技术。

二、正文

我们知道一个窗口对映一个对象,很自然设计一个类.

如下图MyWin *p1和MyWin *p2,在类里有窗口的句柄,这样就进入到window的程序世界里了.

通过类可以找到窗口句柄,但通过窗口句柄怎么找到类.这就是一个比较难办的问题。

WTL的解决方法叫Thunk技术。

 

 

Thunk的定义:

它是一小段代码,它在运行时临时生成的。为了在调用"actual"函数时能被执行,

有时候非常重要。一个普通的功能是转化一个C++的功能对象到一个正常的函数指针。

通常要求插入一个增加的指针,这个指针能表现指向此功能对象的"this"指针.

下面我们来讲解Thunk技术。先从汇编的知识开始。

通过下图我们可以看到WinProc的参数的压栈方法,这是典型的__stdcall调用方式(右边先压栈),当ESP指向Return Addr时,

通过地址+4可以找到hWnd,通过+8可以找到msg.

===================================

补充:有些人认为不是ESP+4找到hWnd,而是+8。会有这样的问题,把ESP看成在哪里的问题。

在Thunk技术里此时的ESP指向Return Addr。而认为要+8的可能此时的ESP指向了push ESP.

下图是子程序调用时的内存情况。

 

===================================

那么是可以通过找到hWnd而找到对象指针呢?

大家可以通过thunk3.cpp文件看到.大约分以下几个步骤:

第一步:窗口调回调函数StartWindowProc

Thunk的Init初始化做了两件事情:1、mov指令替换hWnd为对象指针,2、jump指令跳转到WindowProc;

然后通过m_Thunk.GetCodeAddress获得Thunk指针.

// 只执行一次
	static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, 
										WPARAM wParam, LPARAM lParam)
	{
		CWindow* pThis;
		WNDPROC pProc;
		pThis = (CWindow*)g_pObject;
		pThis->Attach(hWnd);
		// 初始化Thunk,做了两件事:1、mov指令替换hWnd为对象指针,2、jump指令跳转到WindowProc
		pThis->m_Thunk.Init((DWORD)WindowProc, pThis);

		// 得到Thunk指针
		pProc = (WNDPROC)pThis->m_Thunk.GetCodeAddress();
		// 调用下面的语句后,以后消息来了,都由pProc处理
		::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);

		return pProc(hWnd, uMsg, wParam, lParam);
	}

 

// 按一字节对齐
#pragma pack(push, 1)
struct tagThunk
{
	DWORD m_mov;  // 4个字节
	DWORD m_this;
	BYTE  m_jmp;
	DWORD m_relproc;
	//关键代码  此段代码在x86或其他平台是不一样的,若想做其它平台的Thunk,可以参考C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include\atlwin.h文件 //////////////////////////////////////
	BOOL Init(DWORD proc, void* pThis)
	{
		m_mov = 0x042444C7; 
		m_this = (DWORD)pThis;  // mov [esp+4], pThis;而esp+4本来是放hWnd,现在被偷着放对象指针了.
		m_jmp = 0xe9;
		// 跳转到proc指定的入口函数 
		m_relproc = (DWORD)((INT_PTR)proc - ((INT_PTR)this + sizeof(tagThunk)));
		// 告诉CPU把以上四条语句不当数据,当指令,接下来用GetCodeAddress获得的指针就会运行此指令
		FlushInstructionCache(GetCurrentProcess(), this, sizeof(tagThunk));
		return TRUE;
	}
	void* GetCodeAddress()
	{
		return this; // 指向this,那么由GetCodeAddress获得的函数pProc是从DWORD m_mov;开始执行的
	}
};
#pragma pack(pop)

 

上面讲到会跳到WindowProc里去,那么我们在来一下WindowProc函数.

static LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, 
										WPARAM wParam, LPARAM lParam)
	{
	    CWindow* pThis;
		pThis = (CWindow*)hWnd; // 强转为对象指针

		return pThis->ProcessWindowMessage(message, wParam, lParam);
	}

 

这样就实现了多个窗口调用时能调用各自的窗口处理函数了,先用下图来给大家说明这点,再贴出代码,.

 

 

 

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	HWND hWnd = NULL;
	MSG msg;
	CWindow* pwnd = NULL;

	g_hInstance = hInstance; // Store instance handle in our global variable

	pwnd = new CWindow();
	pwnd->RegisterClassEx(hInstance);

	pwnd->Create(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
				CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
	pwnd->ShowWindow(nCmdShow);
	pwnd->UpdateWindow();

	// Thunk技术的好处是可以创建多个窗口。
	/*
	CWindow wnd;
		wnd.Create(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
					CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
		wnd.ShowWindow(nCmdShow);
		wnd.UpdateWindow();
	*/
	
	// Main message loop:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	delete pwnd;
	return (int) msg.wParam;
}

 

 

 

 

=============================================================================================================================

在了解了Thunk技术的原理后,我们来看WTL8中Thunk技术的实现。

先了解WTL中各类的承关系,其中CWindowImpl是我们常继承开发的类.

查看文件C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include\atlwin.h

template <class T, class TBase /* = CWindow */, class TWinTraits /* = CControlWinTraits */>
class ATL_NO_VTABLE CWindowImpl : public CWindowImplBaseT< TBase, TWinTraits >
{
public:
	DECLARE_WND_CLASS(NULL)

 

template <class TBase = CWindow, class TWinTraits = CControlWinTraits>
class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase >
{
public:
	WNDPROC m_pfnSuperWindowProc;

	CWindowImplBaseT() : m_pfnSuperWindowProc(::DefWindowProc)
	{}

	static DWORD GetWndStyle(DWORD dwStyle)
	{
		return TWinTraits::GetWndStyle(dwStyle);
	}
	static DWORD GetWndExStyle(DWORD dwExStyle)
	{
		return TWinTraits::GetWndExStyle(dwExStyle);
	}

	virtual WNDPROC GetWindowProc()
	{
		return WindowProc;
	}
	static LRESULT CALLBACK StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	HWND Create(HWND hWndParent, _U_RECT rect, LPCTSTR szWindowName,
			DWORD dwStyle, DWORD dwExStyle, _U_MENUorID MenuOrID, ATOM atom, LPVOID lpCreateParam = NULL);
	BOOL DestroyWindow()
	{
		ATLASSERT(::IsWindow(m_hWnd));
		return ::DestroyWindow(m_hWnd);
	}

 

template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{//     下面为多线程考虑,而用到了链表.链表数据的插入可参考Create(
	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();
	ATLASSERT(pThis != NULL);
	if(!pThis)
	{
		return 0;
	}
	pThis->m_hWnd = hWnd;

	// Initialize the thunk.  This is allocated in CWindowImplBaseT::Create,
	// so failure is unexpected here.
         // pThis->GetWindowProc()其实就是WindowProc
	pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
	WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
	WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
#ifdef _DEBUG
	// check if somebody has subclassed us already since we discard it
	if(pOldProc != StartWindowProc)
		ATLTRACE(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n"));
#else
	(pOldProc);	// avoid unused warning
#endif
	return pProc(hWnd, uMsg, wParam, lParam);
}


 

template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
	// set a ptr to this message and save the old value
	_ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);

 

 

 是不是很熟悉。

=============================================================================================================================
其实Thunk技术的应用决非只用于窗口管理。下面通过一个CSDN博友的博客开给大家一点思路.

http://blog.csdn.net/tttyd/article/details/4562233

//涂远东 2009 09 17 深圳
//声明函数类型。
typedef void (*TESTFUN)(void*);
//定义修改代码的结构。
#pragma pack(push,1)
struct Thunk
{
	DWORD   m_mov;          // 修改参数指令
	DWORD   m_this;         //修改后的参数
	BYTE    m_jmp;		// jmp TESTFUN,跳转指令。
	DWORD   m_relproc;	// relative jmp,相对跳转的位置。
	//初始化跳转代码。
	void Init(TESTFUN pFun, void* pThis)
	{
		//设置参数指令
		m_mov = 0x042444C7;  //C7 44 24 0C
		//设置修改后的参数
		m_this = PtrToUlong(pThis);

		//设置跳转指针。
		m_jmp = 0xe9;
		
		//设置跳转的相对地址。
		m_relproc = (int)pFun - ((int)this+sizeof(Thunk));
		//把CPU里的缓冲数据写到主内存。
		FlushInstructionCache(GetCurrentProcess(),
			this, sizeof(Thunk));
	}
};
#pragma pack(pop)
//测试动态修改内存里的指令数据。
class CTest
{
public:
	//保存动态修改代码的内存。
	Thunk m_Thunk;
	//真实运行的函数。
	static void TestFun(void* p)
	{
		CTest* pTest = (CTest*)p;
		pTest->Print();
	}
	void Print()
	{
		printf("这里仅仅是一个测试/n TestFun函数的参数被修改了/n");
	}
};

int main(int argc, char* argv[])
{
	//如下调用这个类:
	//测试运行。
	CTest Test;
	Test.m_Thunk.Init(Test.TestFun,&Test);
	TESTFUN pTestFun = (TESTFUN)&(Test.m_Thunk);
	char* psz = "test";
	pTestFun((void*)psz); 
	return 0;
}

 

 

抱歉!评论已关闭.