绪论
我们都知道使用MFC可以快速创建窗口,并处理一些消息。但是MFC到底是如何创建窗口,微软是如何在win32的基础上,按照面向对象的思想实现对窗口的创建的。
在上一篇文章中介绍了MFC的程序是如何启动的。它主要是通过全局变量CWinApp theApp来进行控制程序的流程的。其主要分为两个步骤
步骤一:对全局变量theApp进行构造。
在theApp的构建中首先将theApp存入MFC的两全局变量 AFX_MODULE_STATE moduleState->m_pCurrentWinAPP(当前模块状态) 和 AFX_MODULE_THREAD_STATE* pThreadState ->pCurrentWinThread(当前模块线程状态)中。
步骤二:进行主函数调用。
MFC内部调用了WinMain主函数,在WinMian中获取theApp,通过AfxWinInit()和theApp的InitApplication()对窗口进行初始化,通过theApp的InitInstance()实现窗口的注册、创建、显示和消息处理函数的定义。通过调用theApp的run函数实现窗口的消息循环。
MFC的程序启动的过程请看我的上一篇文章MFC程序启动过程。
本文章将会深入对theApp的InitInstance()的解析,看看MFC是如何实现窗口的注册、创建、显示和消息处理函数的定义。
MFC窗口的创建
InitInstance()函数的重写
我们对InitInstance()进行了重写,重写内容如下:
BOOL CMyWinApp::InitInstance ()
{
CMyFrameWnd* pFrame = new CMyFrameWnd(); //创建框架类
pFrame->Create(NULL,_T("MFCBase")); //创建窗口
m_pMainWnd = pFrame; //将框架类对象保存到theApp的属性中
pFrame->ShowWindow(SW_SHOW); //显示窗口
pFrame->UpdateWindow();
return TRUE;
}
在InitInstance中我们主要的工作是创建CMyFrameWnd(自定义的CFrameWnd的子类)的对象pFrame,通过pFrame实现了窗口的注册、创建、显示。进行我们就详细的研究pFrame,因为框架类(CFrame)就是管理我们窗口的类。
钩子函数
在介绍窗口创建过程之前,我们要介绍一下钩子函数,它在窗口创建过程起着至关重要的作用。钩子函数是一个非常复杂的概念,为了简化介绍,我们只介绍和窗口创建相关的“钩子”
简介
钩子函数是Windows消息处理机制的一部分,通过设置“钩子”,应用程序可以在系统级对所有消息、事件进行过滤,访问在正常情况下无法访问的消息。钩子的本质是一段用以处理系统消息的程序,通过系统调用,把它挂入系统。
windows的钩子函数可以认为是windows的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。通过“钩挂”,您可以给windows一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴趣的事件时,windows都将调用该函数。
创建钩子
HHOOK SetWindowsHookEx(
int idHook, //钩子类型 (WH_CBT)对窗口创建感兴趣 当窗口创建时该钩子就会将程序钩到钩子处理函数
HOOKPROC lpfn, //钩子处理函数
HINSTANCE hMod, //应用程序句柄
DWORD dwThreadId //线程ID
);
钩子处理函数
LRESULT CALLBACK CBTProc(
int nCode, //钩子码(HCBT_CREATEWND) 对应WH_CBT 对窗口创建感兴趣
WPARAM wParam, //刚刚创建成功的窗口句柄
LPARAM lParam
);
更改窗口消息处理函数
LONG_PTR SetWindowLongPtr(
HWND hWnd, //窗口句柄
int nIndex, //GWLP_WNDPROC
LONG_PTR dwNewLong //新的窗口处理函数名(函数地址)
);
SetWindowLongPtr可以更改窗口处理消息处理函数,在钩子处理函数中,可以调用该函数执行我们自己的消息处理函数。
窗口创建过程
在跟踪创建的过程中,我们需要从 pFrame->Create(NULL,_T(“MFCBase”));进入,跟踪程序的执行过程,跟踪过程如下
CMyFrameWnd* pFrame = new CMyFrameWnd();
PFrame->Create(NULL,"MFCBase");
//在程序中我们只传递两个值,其他的参数都由默认值, PFrame->Create的执行过程如下(关注一下第一个参数,我们赋值为NULL):
PFrame->Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName,
DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd,
LPCTSTR lpszMenuName,
DWORD dwExStyle
CCreateContext* pContext)
{
//首先进行加载菜单
HMENU hMenu = NULL;
if (lpszMenuName != NULL)
{
//加载菜单
}
//然后进入CreateEx CreateEX内部执行见下面
if (!CreateEx(dwExStyle,lpszClassName,lpszWindowName,dwStyle,..,pParentWnd->GetSafeHwnd(),hMenu,(LPVOID)pContext))
{
//创建失败
return FALSE;
}
return TRUE;
}
CreateEx内部执行
BOOL CWnd::CreateEx(...,lpszClassName(NULL),....)
{
//创建CREATESTRUCT的结构体并赋值 该结构体存放这Win32API CreateWindowEx的全部12个参数
CREATESTRUCT cs;
...
cs.lpszClass = lpszClassName;(NULL) //窗口的名字为空无法创建窗口
...
cs.hInstance = AfxGetInstanceHandle(); //该函数可以获得当前窗口的实例
...
//通过PreCreateWindow(cs);对lpszClass赋默认值并且进行窗口注册 PreCreateWindow()内部执行见下面
if(!PreCreateWindow(cs))
{
//注册失败
PostNcDestroy();
return FALSE;
}
//在上面的PreCreateWindow中对窗口类进行赋值时,将窗口的消息处理函数设置为DefWindowProc(默认的窗口处理函数),
//我们无法对消息进行处理,接下来,我们需要更改窗口的消息处理函数
//更改窗口的消息处理函数,并且将Frame对象存储到全局变量AFX_THREAD_STATE* pThreadState中,其执行过程在下面
AfxHookWindowCreate(this);//此处的this为Frame
//接下来调用Win32的CreateWindowEx进行窗口的创建
HWND hWnd = CreateWindowEx(...);
//在窗口创建成功后就会产生WM_CREATE(窗口创建成功)的消息,此时就会上钩子函数获得该消息,
//并通过钩子处理函数改变窗口的消息处理函数
//钩子处理函数(_AfxCbtFilterHook)的执行过程在下面
}
PreCreateWindow()内部执行
BOOL CFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
//lpszClass代表着lpszClassName, 在Windows中每一个窗口都要有一个窗口名,我们在 PFrame->Create()将它赋值为NULL,
//因此MFC会为窗口赋值一个默认的窗口名,该名为"AfxFrameOrView140sud"(不同的版本不一样)。
if(cs.lpszClass == NULL)
{
//AfxDeferRegisterClass实现了窗口的注册功能 AfxDeferRegisterClass(AFX_WNDFRAMEORVIEM_REG)执行过程在下面
VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEM_REG));
//_afxWndFrameOrView是一个char类型的数组,值为"AfxFrameOrView140sud",是MFC中lpszClass的默认名。
cs.lpszClass = _afxWndFrameOrView;
}
return TRUE;
}
PreCreateWindow()执行完毕回到CreateEx内部执行
AfxDeferRegisterClass(AFX_WNDFRAMEORVIEM_REG)执行过程
BOOL AfxEndDeferRegisterClass(LONG fToRegister)
{
//创捷窗口类的结构体
WNDCLASS wndcls;
//给窗口类赋值
...
//将窗口的消息处理函数赋值为默认的窗口消息处理函数,那么我们如何进行窗口的消息处理呢?因此会用到后面的钩子函数
wndcls.lpfnWndProc = DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
...
wndcls.style = ...
wndcls.hbrBackground = ...
//_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView,AFX_IDI_STD_FRAME)该函数给wndcls的lpszClassName和hIcon赋值,
//并且对窗口进行注册。_AfxRegisterWithIcon的执行过程在下面
if(_AfxRegisterWithIcon(&wncls,_afxWndFrameOrView,AFX_IDI_STD_FRAME))
{
...
}
}
_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView,AFX_IDI_STD_FRAME)的执行过程
_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView,AFX_IDI_STD_FRAME)
{
//给窗口的名赋默认值
wndcls.lpszClassName = _afxWndFrameOrView; //"AfxFrameOrView140sud"
//加载图标
//使用AfxRegisterClass(wndcls)对窗口进行注册,AfxRegisterClass(wndcls)的执行过程如下
return AfxRegisterClass(wndcls);
{
//调用Win32的RegisterClass进行注册窗口,
if(!RegisterClass(wndcls))
{
//注册失败
return FAlSE;
}
return true;
}
}
完成窗口注册回到PreCreateWindow()内部执行
AfxHookWindowCreate(this)的执行过程
void AFXAPI AfxHookWindowCreate(CWnd* pWnd) //该pWnd是pFrame
{
//获取第三个全局变量 _AFX_THREAD_STATE* pThreadState 当前线程状态信息
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
if (pThreadState->m_hHookOldCbtFilter == NULL)
{
//设置窗口创建的钩子函数,其中_AfxCbtFilterHook是钩子的处理函数
pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook,NULL,::GetCurrentThreadId());
//经Frame对象存储到全局变量 _AFX_THREAD_STATE* pThreadState中
pThreadState->m_pWndInit = pWnd;
}
}
钩子函数设置成功,回到CreateEx内部执行
_AfxCbtFilterHook钩子处理函数执行过程如下
当窗口创建成功后就会通过钩子函数AfxHookWindowCreate(this)钩到_AfxCbtFilterHook,_AfxCbtFilterHook的代码如下
_AfxCbtFilterHook(int code, WPARAM wParam(当前创建成功的窗口句柄),LPARAM lParam)
{
//将Frame对象与窗口句柄进行绑定
//获取全局变量_AFX_THREAD_STATE* pThreadState 当前线程状态信息
_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
//获取Frame对象
CWnd* pWndInit = PThreadState->m_pWndInit;
//获取窗口句柄
HWND hWnd = (HWND)wParam;
//将Frame对象与窗口句柄进行绑定 ,Attach的执行过程在下面
pWndInit->Attach(hWnd);
//更改窗口的消息处理函数
WNDPROC afxWndProc = AfxGetAfxWndProc();
//更改窗口消息处理函数为AfxWndProc
oldWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
}
Attach的执行过程
BOOL CWnd::Attach(HWND hWndNew) //hWndNew是当前窗口句柄
{
//创建Map集合 afxMapHWND(true)的执行过程如下
CHandleMap* pMap = afxMapHWND(true);
{
//获取全局变量 AFX_MODULE_THREAD_STATE* pThreadState 当前程序模块线程状态信息
AFX_MODULE_THREAD_STATE8* pState = AfxGetModuleThreadState();
//新建映射类对象,并保存到全局变量AFX_MODULE_THREAD_STATE* pThreadState的m_pmapHWND的成员中
pState->m_pmapHWND = new CHandleMap(RUNTIME(CWnd),ConstructDestruct<CWnd>::Construct,
ConstructDestruct<CWnd>::Destruct,offsetof(CWnd,m_hWnd));
return pState->m_pmapHWND;
}
//将pFrame中的窗口句柄与当前窗口句柄进行绑定,双向绑定,即有pFrame能找到句柄,有句柄能找到pFrame,
//此时pFrame->m_hWnd = (当前窗口句柄) //SetPermanent()的执行过程如下:
pMap->SetPermanent(this->m_hWnd = hWndNew,this);
{
//实现由句柄得到Frame,将Frame存放到以句柄为索引的数组中,
//且该数组存放在全局变量AFX_MODULE_THREAD_STATE* pThreadState中
m_permanentMap[窗口句柄] = Frame;
}
}
总结
MFC创建窗口的步骤分为一下几步
-
加载菜单
-
调用CWnd::CreateEx函数创建窗口
在CteateEx中调用PreCreateWindow函数设计和注册窗口类
在PreCreateWindow函数中调用AfxDeferRegisterClass函数进行设计窗口类
在AfxDeferRegisterClass内部
WNDCLASS wndcls;//设计窗口类
定义窗口的消息处理函数为DefWindowProc
调用_AfxRegisterWithIcon函数,处理图标,并调用AfxRegisterClass函数进行窗口注册,在AfxRegisterClass内部才真正到调用了win32的RegisterClass进行窗口的注册。
-
调用AfxHookWindowCreate函数
在AfxHookWindowCreate内部,调用SetWindowsHookEx创建WH_CBT类型的钩子,钩子的处理函数是_AfxCbtFilterHook。并且将框架类对象保存到全局变量当前啊程序线程信息中
-
调用CreateWindowEx函数创建窗口,窗口创建成功后马上调用钩子的处理函数
-
在钩子处理函数_AfxCbtFilterHook中将框架类对象与窗口句柄进行绑定和更改窗口的消息处理函数为AfxWndProc();
结语
通过以上对窗口创建过程分析,我们可以知道 pFrame->Create(NULL,_T(“MFCBase”));实现了如下功能:
- 加载菜单
- 将为空的窗口名赋值为默认值AfxFrameOrView140sud
- 进行窗口的注册
- 更改窗口消息处理函数为AfxWndProc();
- 将框架对象与窗口句柄进行绑定 ,这样一个框架类就可以代表一个窗口,符合面向对象的编程思想。
经过上述过程就可以完成对一个窗口的创建,那么窗口创建后如何进行消息处理呢?这个问题将会在我的下一篇文章中进行介绍–MFC的消息处理机制
4900

被折叠的 条评论
为什么被折叠?



