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 ...
随机推荐
- U盘装系统
http://jingyan.baidu.com/article/fec4bce20e344cf2618d8b37.html
- 获取广告标识符ifad
#import <AdSupport/ASIdentifierManager.h> NSString *adId =[[[ASIdentifierManager sharedManager ...
- bzoj3051: [wc2013]平面图
Description Input Output 扫描线求出平面图的对偶图然后求最小生成树,用并查集按秩合并,以便查询两点间路径最大权 #include<stdio.h> #include ...
- Spring Boot(3)---自定义spring boot starter 问题
1. "Failed to process import candidates for configuration class [com.simple.....]": 主要原因: ...
- 读写hdfs文件(工作笔记)
import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Map; ...
- php 获取远程图片保存到本地
php 获取远程图片保存到本地 使用两个函数 1.获取远程文件 2.把图片保存到本地 /** * 获取远程图片并把它保存到本地 * $url 是远程图片的完整URL地址,不能为空. */ functi ...
- Linux中执行shell脚本的4种方法总结
bash shell 脚本的方法有多种,现在作个小结.假设我们编写好的shell脚本的文件名为hello.sh,文件位置在/data/shell目录中并已有执行权限. 方法一:切换到shell脚本所在 ...
- selenium之 定位以及切换frame(iframe)
Set<String> windows = driver.getWindowHandles(); int count = 0; for(String handl ...
- Angular进度-1207
https://www.angular.cn/docs/ts/latest/tutorial/toh-pt1.html
- 评论alpha版本发布
讲解顺序: 1. 新蜂:俄罗斯方块 俄罗斯方块已经完成了核心的游戏部分,可以流畅的进行游戏,经验值功能也已经完成,目前进度很好:不足之处主要有:后续的显示内容还没完成,所以界面空出来很多板块,alp ...