ATL中窗口句柄与窗口过程的关联方法
ATL中采用了一种动态生成机器指令的方式进行窗口句柄与窗口对象进行关联,以是详细分析:
CWindowImpl会在第一次调用Create时注册窗口类,该窗口类是的信息是在CWindowImpl的子类中使用
DECLARE_WND_CLASS定义的,该宏会为CWindowImpl的子类定义一个静态成员函数GetWndClassInfo,
该函数返回一个CWndClassInfo结构体,其中包含了WNDCLASSEX,用于指定该类的窗口类注册时所用的
WNDCLASSEX结构。
在DECLARE_WND_CLASS指定的默认的窗口过程是StartWindowProc,该函数是CWindowImplBaseT
的静态成员函数,用于第一次收到消息时将窗口对象与窗口句柄关联(参见下文),StartWindowProc函数
定义如下所示:
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
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->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);
}
函数首先通过_AtlWinModule.ExtractCreateWndData()获取到当前正在创建的窗口的CWindowImpl对象的this指针,
实际上,在_AtlWinModule中保存了一个链表,用于保存当前正在创建窗口的窗口对象的指针,当调用CWindowImpl的
Create方法时将该对象的指针到链表中,在StartWindowProc时取出,这个链表是根据ThreadID进行关联的,所以可以
保证在多个线程创建窗口是,可以在StartWindowProc取到正确的this指针。
取到对象的this指针后,首先对其m_hWnd赋值,然后初始化该对象的thunk成员(用于实现窗口和窗口对象关联的关键
对象)。该结构定义如下:
class CWndProcThunk
{
public:
_AtlCreateWndData cd;
CStdCallThunk thunk; BOOL Init(WNDPROC proc, void* pThis)
{
return thunk.Init((DWORD_PTR)proc, pThis);
}
WNDPROC GetWNDPROC()
{
return (WNDPROC)thunk.GetCodeAddress();
}
}; struct _AtlCreateWndData
{
void* m_pThis;
DWORD m_dwThreadID;
_AtlCreateWndData* m_pNext;
}; #if defined(_M_IX86) || defined (_M_AMD64) #pragma pack(push,8)
class CDynamicStdCallThunk
{
public:
_stdcallthunk *pThunk; CDynamicStdCallThunk()
{
pThunk = NULL;
} ~CDynamicStdCallThunk()
{
if (pThunk)
{
delete pThunk;
}
} BOOL Init(DWORD_PTR proc, void *pThis)
{
if (pThunk == NULL)
{
pThunk = new _stdcallthunk;
if (pThunk == NULL)
{
return FALSE;
}
}
return pThunk->Init(proc, pThis);
} void* GetCodeAddress()
{
return pThunk->GetCodeAddress();
}
}; #pragma pack(pop)
typedef CDynamicStdCallThunk CStdCallThunk;
#else
typedef _stdcallthunk CStdCallThunk;
#endif
CWndProcThunk中的成员cd就是上文所述的_AtlWinModule.ExtractCreateWndData()中保存的创建信息,成员thunk用于窗口和窗口对象关联。定义如下:
struct _stdcallthunk
{
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
BOOL Init(DWORD_PTR proc, void* pThis)
{
m_mov = 0x042444C7; //C7 44 24 0C
m_this = PtrToUlong(pThis);
m_jmp = 0xe9;
m_relproc = DWORD((INT_PTR)proc - ((INT_PTR)this+sizeof(_stdcallthunk)));
// write block from data cache and
// flush from instruction cache
FlushInstructionCache(GetCurrentProcess(), this, sizeof(_stdcallthunk));
return TRUE;
}
//some thunks will dynamically allocate the memory for the code
void* GetCodeAddress()
{
return this;
}
void* operator new(size_t)
{
return __AllocStdCallThunk();
}
void operator delete(void* pThunk)
{
__FreeStdCallThunk(pThunk);
}
};
该结构实际上是一段代码,用于替换真正的窗口过程,该代码被StartWindowProc通过SetWindowLongPtr设置为窗口的窗口过程,由于系统调用窗口过程是采用的是stdcall,
所以会将窗口过程的参数从逆序压栈,窗口过程的原型如下:
typedef LRESULT (CALLBACK* WNDPROC)(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
所以在调用m_thunk时栈内容如下:
[esp + 00]-->| |
[esp + 04]-->| HWND |
[esp + 08]-->|message|
[esp + 0C]-->|wParam |
[esp + 10]-->|lParam |
thunk中的代码等效于
mov dword ptr [esp+0x4], pThis
jmp WndProc
实际上就是把栈上hwnd参数修改为窗口对象的this指针,并跳转到窗口对象的WindowProc函数
(默认为CWindowImplBaseT< TBase, TWinTraits >::WindowProc,是个静态函数)。
而thunk的成员pThis和m_realproc是在StartWindowProc中初始化化的。
这样在下次系统调用窗口过程时,就会支持thunk的代码,并跳转到指定的WindowProc函数中,
在WindowProc中会将hwnd转化为this指针,并调用对应的对象进行消息处理。
CWindowImplBaseT< TBase, TWinTraits >::WindowProc定义如下:
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);
const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
pThis->m_pCurrentMsg = &msg;
// pass to the message map to process
LRESULT lRes;
BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
// restore saved value for the current message
ATLASSERT(pThis->m_pCurrentMsg == &msg); // do the default processing if message was not handled
if(!bRet)
{
if(uMsg != WM_NCDESTROY)
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
else
{
// unsubclass, if needed
LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
// mark window as destryed
pThis->m_dwState |= WINSTATE_DESTROYED;
}
}
if((pThis->m_dwState & WINSTATE_DESTROYED) && pOldMsg== NULL)
{
// clear out window handle
HWND hWndThis = pThis->m_hWnd;
pThis->m_hWnd = NULL;
pThis->m_dwState &= ~WINSTATE_DESTROYED;
// clean up after window is destroyed
pThis->m_pCurrentMsg = pOldMsg;
pThis->OnFinalMessage(hWndThis);
}else {
pThis->m_pCurrentMsg = pOldMsg;
}
return lRes;
}
ATL中窗口句柄与窗口过程的关联方法的更多相关文章
- JS 在open打开的子窗口页面中调用父窗口页面的JS方法
需求的情景如下: 1:做新增或修改等操作的时候打开一个新的浏览器窗口(使用window.open(参数等)方法) 2:在新增或修改等的页面上有返回按钮.重置按钮.保存按钮,对于返回就直接关闭此窗口(使 ...
- 从普通函数到对象方法 ------Windows窗口过程的面向对象封装
原文地址:http://blog.csdn.net/linzhengqun/article/details/1451088 从普通函数到对象方法 ------Windows窗口过程的面向对象封装 开始 ...
- win32程序通过LPCREATESTRUCT中的lpCreateParams传递参数给窗口过程函数
win32窗口程序中如果需要给窗口过程函数传递自定义参数,可以通过LPCREATESTRUCT结构体中的lpCreateParams进行传递. 创建窗口实例函数: m_hWnd = CreateWin ...
- C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常,不存在从对象类型System.Windows.Forms.DateTimePicker到已知的托管提供程序本机类型的映射。
一:C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常 其实,这个问题与C#的垃圾回收有关.垃圾回收器管 理所有的托管对象,所有需要托管数据的.NET语言(包括 C#)都受运行库的 垃圾回收器 ...
- (转)C#在父窗口中调用子窗口的过程(无法访问已释放的对象)
C#在父窗口中调用子窗口的过程: 1. 创建子窗口对象 2. 显示子窗口对象 笔者的程序中,主窗体MainFrm通过菜单调用子窗口ChildFrm.在窗体中定义了子窗口对象,然后在菜单项点击事件中 ...
- win32编程中消息循环和WndProc()窗口过程函数
原文地址:https://blog.csdn.net/zxxSsdsd/article/details/45504383 在win32程序的消息循环函数中 while (GetMessage (&a ...
- 理解ATL中的一些汇编代码(通过Thunk技术来调用类成员函数)
我们知道ATL(活动模板库)是一套很小巧高效的COM开发库,它本身的核心文件其实没几个,COM相关的(主要是atlbase.h, atlcom.h),另外还有一个窗口相关的(atlwin.h), 所以 ...
- C#调用WinAPI及窗口过程
C#调用WINAPI及Windows窗口消息的发与送 最近在做一个餐饮项目(C#Winform),其中有一块是做点菜宝接口的对接,点菜宝的厂商提供了一个WX.exe的驱动程序,这个驱动程序无直接打开, ...
- MSG结构体和WndProc窗口过程详解
MSG结构体和WndProc窗口过程对于Windows编程非常重要,如果不了解它们,可以说就没有学会Windows编程. MSG结构体 MSG 结构体用来表示一条消息,各个字段的含义如下: typed ...
随机推荐
- Asp.net MVC 的八个扩展点
http://www.cnblogs.com/richieyang/p/5180939.html MVC模型以低耦合.可重用.可维护性高等众多优点已逐渐代替了WebForm模型.能够灵活使用MVC提供 ...
- SQL 用户定义表类型,在存储过程里使用表类型,表参数作参数
.定义表类型SUTDENTTYPE,包含三个字段,分别对应学生表的NAME,SEX和PHONE.之所以如此创建,我是准备在插入新学生数据的存储过程中,以它为参数. GO CREATE TYPE S ...
- [API]使用Blueprint来高雅的编写接口文档 前后端api文档,移动端api文档
网址:http://apiary.io/ 介绍:一款非常强大的前后端交互api设计编辑工具(编辑器采用Markdown类似的描述标记,非常高效),高颜值的api文档,还能生成多种语言的测试代码. 中文 ...
- Git 常见问题: unable to negotiate with *.*.*.*: no matching key exchange methodfound...
在Windows上更新了git 版本后,clone/pull时出现错误, unable to negotiate with *.*.*.*: no matching key exchange meth ...
- linux的零碎使用
一.Linux(rehat.centos.ubuntu...)基础知识 上午: putty软件连接linux服务器: [root @ foundation2 ~ ] # 用户名 ...
- VR外包团队:长年承接VR虚拟现实外包(应用、游戏、视频、漫游等)
北京动点飞扬软件,从事外包业务五年,长年承接全景VR视频,全景普通视频外包. 以下是全景VR视频案例(可操作,人不动景物不动,人移动,景物跟随) 欢迎联系我们QQ:372900288 TEL:1391 ...
- 小表驱动大表, 兼论exists和in
给出两个表,A和B,A和B表的数据量, 当A小于B时,用exists select * from A where exists (select * from B where A.id=B.id) ex ...
- OAF_文件系列11_实现OAF读写Excel包JXL和POI的区别(概念)
20150803 Created By BaoXinjian
- 禁用Windows窗体的关闭按钮
1. protected override void OnFormClosing(FormClosingEventArgs e) { e.Cancel = true; base.OnFormClosi ...
- bootstrap-提示标签、提示框
提示标签: <body> <div class="container"> <div class="row"> <div ...