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中窗口句柄与窗口过程的关联方法的更多相关文章

  1. JS 在open打开的子窗口页面中调用父窗口页面的JS方法

    需求的情景如下: 1:做新增或修改等操作的时候打开一个新的浏览器窗口(使用window.open(参数等)方法) 2:在新增或修改等的页面上有返回按钮.重置按钮.保存按钮,对于返回就直接关闭此窗口(使 ...

  2. 从普通函数到对象方法 ------Windows窗口过程的面向对象封装

    原文地址:http://blog.csdn.net/linzhengqun/article/details/1451088 从普通函数到对象方法 ------Windows窗口过程的面向对象封装 开始 ...

  3. win32程序通过LPCREATESTRUCT中的lpCreateParams传递参数给窗口过程函数

    win32窗口程序中如果需要给窗口过程函数传递自定义参数,可以通过LPCREATESTRUCT结构体中的lpCreateParams进行传递. 创建窗口实例函数: m_hWnd = CreateWin ...

  4. C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常,不存在从对象类型System.Windows.Forms.DateTimePicker到已知的托管提供程序本机类型的映射。

    一:C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常 其实,这个问题与C#的垃圾回收有关.垃圾回收器管 理所有的托管对象,所有需要托管数据的.NET语言(包括 C#)都受运行库的 垃圾回收器 ...

  5. (转)C#在父窗口中调用子窗口的过程(无法访问已释放的对象)

    C#在父窗口中调用子窗口的过程: 1. 创建子窗口对象 2. 显示子窗口对象   笔者的程序中,主窗体MainFrm通过菜单调用子窗口ChildFrm.在窗体中定义了子窗口对象,然后在菜单项点击事件中 ...

  6. win32编程中消息循环和WndProc()窗口过程函数

    原文地址:https://blog.csdn.net/zxxSsdsd/article/details/45504383 在win32程序的消息循环函数中  while (GetMessage (&a ...

  7. 理解ATL中的一些汇编代码(通过Thunk技术来调用类成员函数)

    我们知道ATL(活动模板库)是一套很小巧高效的COM开发库,它本身的核心文件其实没几个,COM相关的(主要是atlbase.h, atlcom.h),另外还有一个窗口相关的(atlwin.h), 所以 ...

  8. C#调用WinAPI及窗口过程

    C#调用WINAPI及Windows窗口消息的发与送 最近在做一个餐饮项目(C#Winform),其中有一块是做点菜宝接口的对接,点菜宝的厂商提供了一个WX.exe的驱动程序,这个驱动程序无直接打开, ...

  9. MSG结构体和WndProc窗口过程详解

    MSG结构体和WndProc窗口过程对于Windows编程非常重要,如果不了解它们,可以说就没有学会Windows编程. MSG结构体 MSG 结构体用来表示一条消息,各个字段的含义如下: typed ...

随机推荐

  1. NPOI读取Excel

    项目环境:Webform framework4.0 dll版本:NPOI2.0  dotnet2.0版本 这两天要做个excel导入的功能,想到以前用过NPOI,感觉很给力,今天写了个DEMO,写的时 ...

  2. awk 的使用

    awk [-F field-separator] 'commands' input-file(s) 其中,commands 是真正awk命令,[-F域分隔符]是可选的.input-file(s) 是待 ...

  3. 崽崽帮www.zaizaibang.com精选14

    [行走贵州]爽爽贵阳,乐活天堂! 北京儿童医院将建遗传代谢病专科医院 [山东十大最难懂方言]原来青岛话还是很好懂滴 ❤如果南宁的儿童医院长这样…… 成都三所小学入围中国百强小学名单 [乐湖新闻]学习中 ...

  4. 加快http请求图片的速度

    在web网页里面经常需要请求图片,为了减少图片的http请求,总共有三种办法 使用map和area,具体看下面的链接 http://www.w3school.com.cn/tags/att_area_ ...

  5. My tool chain in CentOS 7

    - Git Client SmartGit http://www.linuxlinks.com/article/20120129035558195/GitClients.html candidates ...

  6. 【与软件无关】2013赤峰地区C1科目三考试攻略【绝对原创】

    期待很久的科目三,终于在开考了.传说中的全部电子评判,让习惯给考官送礼的赤峰人民无所是从.据说前几天曾经有一个驾校,考了一整天,八十多个人一个没过的. 我这个攻略是今天通过考试后的一点心得,希望能有用 ...

  7. js加解密

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. 获取layer.open弹出层的返回值

    正在开发的车联网项目用到了layer API.当我在开发“新建电子围栏”的时候需要弹出地图,用户在地图中画一个区域,最后将这个弹出层的数据返回给原页面.下面是我的实现过:程: 触发弹出层的代码: la ...

  9. terminator 安装及使用

    1. 安装 $ sudo apt-get install terminator 2. 右键设置首选项 背景设置为0.8透明度, 字体挤在一起:在ubuntu下请选择mono后缀的字体就可以了 3. 使 ...

  10. [REP]AWS Regions and Availability Zones: the simplest explanation you will ever find around

    When it comes to Amazon Web Services, there are two concepts that are extremely important and spanni ...