钩子函数
钩子属于win32技术,具有优先勾取消息的权利:当一个消息产生时,钩子勾取消息进行处理,然后消息才送回程序
接下来以一个勾取窗口创建消息的钩子为例进行讲解
钩子类型有键盘钩子,鼠标钩子,WH_CBT钩子等等。钩子只勾取自己类型的消息,如WH_CBT钩子只勾取窗口创建的消息
钩子执行过程:钩子只勾取指定应用程序指定线程且属于自己类型的消息,然后触发钩子处理函数。当应用程序实例句柄为空时,则勾取所有应用程序的消息。当线程ID为空时,则勾取指定应用程序的线程的消息
钩子处理函数:钩子码的值取决于钩子类型,每种类型都有它自己唯一的特征钩子码
该函数常常与钩子函数配合使用,通过第二个参数的值可以更改指定窗口的窗口处理函数或者窗口风格等等。
如上图,第二个参数值为GWLP_WNDPROC意味着该函数用于更改窗口处理函数
窗口创建过程
首先创建一个MFC窗口
这是我们上文写的MFC程序所涉及的类
接下来我们通过断点调试来观察窗口是怎么建立的
现在我们进入函数内部,观察Create的执行过程
CMyFrameWnd* pFrame = new CMyFrameWnd;
pFrame->Create(NULL, "MFCCreate")//函数内部this为pFrame(自己new框架类对象地址)。参数1类名称 为lpszClassName,即窗口类名称。接下来注意NULL的动向
{
//当菜单不为空时
HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU);
::LoadMenu(hInst, lpszMenuName)//加载菜单
this->m_strTitle = lpszWindowName;//框架类成员保存标题名
//当菜单为空时
CreateEx(..., NULL,...)//创建菜单。此时的NULL便是最开始Create的NULL,函数内部this为pFrame
{
CREATESTRUCT cs;//该结构体共有十二个成员,分别表示窗口的十二个属性,对应CreateEx的十二个参数
cs.lpszClass = NULL;//此处复制存在问题,但下面将更改正确
....
....
cs.hInstance = AfxGetInstanceHandle();//获取应用程序实例句柄
this->PreCreateWindow(cs)//注册窗口类并对cs中为NULL的成员重新赋值
{
AfxDeferRegisterClass(...)//注册窗口类
{
WNDCLASS wndcls;//该窗口类共有十个成员
...//窗口类成员赋值。但此时并没有赋值完全,而是在后续慢慢赋值
wndcls.lpfnWndProc = DefWindowProc;//此处为默认窗口处理函数,下面的钩子处理函数将更改我们自己的窗口处理函数
...//赋值剩余的窗口类成员
_AfxRegisterWithIcon(&wndcls, "AfxFrameOrView100sd"..)
{
&wndcls->lpszClassName = "AfxFrameOrView100sd";//赋值最后的窗口类名称
::RegisterClass(&wndcls)//注册窗口类
//此时仍未修改窗口处理函数
}
}
}
AfxHookWindowCreate(pFrame)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();//获取全局变量&ccc(当前程序线程信息)
::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,...);//利用Win32的API函数,埋下一个类型为WH_CBT的钩子
pThreadState->m_pWndInit = pFrame;//将自己new的框架类对象pFrame保存到全局变量ccc的一个成员中。
}
::CreateWindowEx(...);//创建窗口,此函数一旦执行成功,立即转到钩子处理函数。
}
}
//钩子处理函数,系统自动调取
//该函数用于关联窗口句柄和框架类对象
_AfxCbtFilterHook(.wParam.)//wParam:创建的窗口的句柄
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();//获取&ccc
CWnd* pWndInit = pThreadState->m_pWndInit;//获取pFrame===pWndInit
HWND hWnd = (HWND)wParam;//刚刚创建成功的框架窗口句柄
pWndInit->Attach(hWnd)//使pFrame与wParam建立联系
{
CHandleMap* pMap = afxMapHWND(TRUE)//pMap为映射类对象地址
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
pState->m_pmapHWND = new CHandleMap(..); //new了一个映射类对象,并将对象地址保存到bbb的一个成员中
return pState->m_pmapHWND;//返回映射类对象地址
}
pMap->SetPermanent(this->m_hWnd = hWnd, pFrame)//函数内部this为pMap
{
m_permanentMap[hWnd] = pFrame;//已知句柄,获取pFrame
}
}
(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,AfxWndProc);//将默认窗口处理函数更改为AfxWndProc(真正的窗口处理函数)
}
MFC是由C++封装编写的,根据C++的思想,窗口类对象是可以代表窗口的,但MFC的本质是Win32,在Windows中,只有窗口句柄才能代表窗口,这就形成了一个矛盾。只有通过窗口句柄关联类对象,类对象才能真正的代表窗口,而这就是_AfxCbtFilterHook钩子处理函数所完成的
窗口处理函数
接前文,钩子函数勾取窗口创建消息以后,跳转到真正的窗口处理函数中。现我们通过添加一个WM_PAINT消息观察该消息处理时是不是真的在真正的窗口处理函数中处理
在上图中自定义了窗口处理函数,但最后返回父类虚函数。这是因为我们为了在MFC库收到WM_CREATE消息时,调用我们自己定义的函数,而其他无关消息交给父类虚函数作做默认消息处理
运行程序以后,我们发现在窗口出现前, 程序先弹窗了我们定义的弹窗,然后再弹出窗口
此时就存在了一个疑问:在上文中,我们发现真正的窗口处理函数是AfxWndProc,但此处却调用了我们自定义的窗口处理函数
通过调用堆栈我们发现,真正的窗口处理函数AfxWndProc调用了我们自定义的窗口处理函数
//真正的窗口处理函数
//以WM_CREATE消息为例,捎带想着点WM_PAINT消息
AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)//我们自行调试时,注意uMsg的值,WM_CREATE的值为1
{
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd)//通过窗口句柄返回框架类对象
{
CHandleMap* pMap = afxMapHWND()
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();//获取&bbb
return pState->m_pmapHWND;//返回之前保存在bbb中的映射类地址
}
pWnd = pMap->LookupPermanent(hWnd)//函数内部this为pMap
{
return m_permanentMap[hWnd];//返回pFrame
}
}