我们也通过一个简单的MFC程序进行研究
1.建一个win32简单应用程序,(因为是不是MFC程序取决于调没调MFC函数)
2. 删除入口函数,只留下#include "stdafx.h"3.将stdafx.h中的头文件 <windows.h> 更改为 <afxwin.h>。
4.Project-->Settings菜单项中设置使用MFC库。
5.编写代码
// MFCCreate.cpp : Defines the entry point for the application.
//
#include "stdafx.h"
class CMyFrameWnd: public CFrameWnd
{
public:
virtual LRESULT WindowProc(UINT message,WPARAM wParam,LPARAM lParam);
};
LRESULT CMyFrameWnd::WindowProc(UINT message,WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_CREATE:
AfxMessageBox("WM_CREATE");
break;
}
return CFrameWnd::WindowProc(message,wParam,lParam);
}
class CMyWinApp:public CWinApp
{
public:
virtual BOOL InitInstance();
};
CMyWinApp theApp;//全局对象
BOOL CMyWinApp::InitInstance()
{
CMyFrameWnd *pFrame = new CMyFrameWnd;
pFrame->Create(NULL,"MFCCreate");
m_pMainWnd = pFrame;
pFrame->ShowWindow(SW_SHOW);
pFrame->UpdateWindow();
return TRUE;
}
是怎么运行起来的???我们来通过加断点跟进微软内部函数来分析
在pFrame->Create(NULL,"MFCCreate");这行加断点,f11进入到create函数,写出伪代码
_AFX_THREAD_STATE ccc;//全局变量(当前程序线程信息)
CMyFrameWnd *pFrame = new CMyFrameWnd;
pFrame->Create( NULL, "MFCCreate" )
//函数内部this为 pFrame(自己new的框架类对象地址)
{
加载菜单资源
CreateEx(..,NULL...)//函数内部this为 pFrame
{
CREATESTRUCT cs;
.....
cs.lpszClass = NULL;//这么赋值肯定有问题,但下面会更改
cs.hInstance = AfxGetInstanceHandle();//全局函数,可以获取winmain函数的第一个参数即实例
句柄
.....
PreCreateWindow(cs)//注册窗口类 和 更改 cs为NULL的成员lpszClass
{
AfxDeferRegisterClass(...)
{
WNDCLASS wndcls;//和CreateWindowEx的12个参数一一对应
wndcls.lpfnWndProc = DefWindowProc;//缺省函数作为窗口处理函数,这么做,自己就不能
处理消息了,而且退不出消息循环,所以用它不行,但下面钩子处理函数中修改
.....
_AfxRegisterWithIcon(&wndcls,"AfxFrameOrView42sd" )
{
&wndcls->lpszClassName = "AfxFrameOrView42sd";
AfxRegisterClass(&wndcls)
{
::RegisterClass(&wndcls);
//注册窗口类 类名称"AfxFrameOrView42sd"。
}
}
}
cs.lpszClass = "AfxFrameOrView42sd"; //lpszClass在这行被修改
}
AfxHookWindowCreate(pFrame)//参数为自己new的框架类对象地址
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
//获取全局变量 &ccc
::SetWindowsHookEx( WH_CBT...);
//利用WIN32 API 函数在程序中埋下一个类型为
//WH_CBT钩子
pThreadState->m_pWndInit = pFrame;
//将自己new的框架类对象地址(pFrame)保存到 ccc中
}
::CreateWindowEx(..);//创建窗口,只要此函数一旦执行成功,WM_CREATE消息立即被钩子勾到钩
子处理函数中。
}
}
**********************************************************
钩子处理函数
_AfxCbtFilterHook(...,wParam,..)
{
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
//获取全局变量 &ccc
CWnd* pWndInit = pThreadState->m_pWndInit;
//重新获取自己new的框架类对象地址pFrame<=>pWndInit
HWND hWnd = (HWND)wParam;
//获取刚刚创建成功 窗口句柄
pWndInit->Attach(hWnd)//函数内部this为pFrame<==>pWndInit
{
CHandleMap* pMap = afxMapHWND(TRUE)
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
//获取全局变量 &bbb
pState->m_pmapHWND = new CHandleMap(...);
//将new出来的映射类对象地址保存全局变量 bbb中
return pState->m_pmapHWND;
}
pMap->SetPermanent(m_hWnd = hWndNew, pFrame)
//函数内部this为pMap(映射类对象地址)
{
m_permanentMap[hWndNew] = pFrame;
}
}
WNDPROC afxWndProc = AfxGetAfxWndProc();
//获取 AfxWndProc函数的地址
::SetWindowLong(hWnd, GWL_WNDPROC,(DWORD)afxWndProc);
//利用WIN32 API 函数将hWnd的处理函数更改为
// AfxWndProc(真正的窗口处理函数)
}
总结上边的伪代码我们可以得到窗口创建机制的原理:
1 加载菜单
2 调用CreateEx成员函数,注册窗口类/创建窗口
2.1 调用PreCreateWindow成员函数 设计并注册窗口类
调用AfxDeferRegisterClass全局函数设计窗口类:
调用 AfxRegisterClass全局函数,在这个函中调用WIN32 API 函数::RegisterClass注册
2.2 调用AfxHookWindowCreate函数
利用::SetWindowsHookEx在程序中埋下一个类型WH_CBT的钩子。
将自己new的框架类对象地址(pFrame)保存到当前程序线程信息中。
2.3 调用WIN32 API 函数 ::CreateWindowEx创建窗口。 此函数一旦执行成功,马上执行钩子处理函数。
3 钩子处理函数
3.1 将 窗口句柄 和 框架类对象 建立一对一绑定关系
3.2 将 窗口处理函数 更改为 AfxWndProc(真正的窗口处理函数)
思考:
1.AfxGetInstanceHandle()全局函数,可以获取winmain函数的第一个参数即实例句柄。
2.上边的出的结论AfxWndProc才是真正的窗口处理函数,为什么我们写的WindowProc它处理消息???
答:因为我们自己写的WindowProc最终会被AfxWndProc调用。
分析:通过断点调试,现在case WM_CREATE:这行加断点,用调用堆栈的方法进入AfxCallWndProc函数中,写出伪代码
AfxWndProc(...)
{
CWnd* pWnd = CWnd::FromHandlePermanent(hWnd)
{
CHandleMap* pMap = afxMapHWND()
{
AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
//获取全局变量 &bbb
return pState->m_pmapHWND;
//返回 保存在bbb中的上面new的 映射类对象地址
}
CWnd *pWnd = (CWnd*)pMap->LookupPermanent(hWnd)
//函数this指针为pMap(映射类对象地址)
{
return m_permanentMap[hWnd];
}
return pWnd;
}
AfxCallWndProc(pWnd..)//参数的pWnd<==>pFrame
{
pWnd->WindowProc(...)
{
//回到自己代码
}
}
}
事实上
AfxWndProc(窗口处理函数)处理消息的执行过程就是:
1 利用消息所属窗口句柄,找到和它绑定在一起框架类对象地址(pFrame)。
2 利用框架类对象地址(pFrame)调用框架类的成员虚函数(WindowProc),完成消息的处理。